summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt4
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt2
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt29
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/LocationInfoCache.kt171
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt10
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt6
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt140
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt8
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt2
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt26
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt3
13 files changed, 217 insertions, 190 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt
index d9df17e9c7..ba94206a73 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt
@@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.ipc
import android.os.Message as RawMessage
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
+import net.mullvad.mullvadvpn.model.GeoIpLocation
import net.mullvad.mullvadvpn.model.Settings
// Events that can be sent from the service
@@ -14,6 +15,9 @@ sealed class Event : Message(), Parcelable {
object ListenerReady : Event(), Parcelable
@Parcelize
+ data class NewLocation(val location: GeoIpLocation?) : Event(), Parcelable
+
+ @Parcelize
data class SettingsUpdate(val settings: Settings?) : Event(), Parcelable
companion object {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt
index 47b613816b..388f60b07e 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Request.kt
@@ -11,7 +11,7 @@ sealed class Request : Message(), Parcelable {
protected override val messageKey = MESSAGE_KEY
@Parcelize
- class RegisterListener(val listener: Messenger) : Request(), Parcelable
+ data class RegisterListener(val listener: Messenger) : Request(), Parcelable
companion object {
private const val MESSAGE_KEY = "request"
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
index 1f4acef73f..e15ab20376 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
@@ -1,11 +1,14 @@
package net.mullvad.mullvadvpn.model
+import android.os.Parcelable
import java.net.InetAddress
+import kotlinx.parcelize.Parcelize
+@Parcelize
data class GeoIpLocation(
val ipv4: InetAddress?,
val ipv6: InetAddress?,
val country: String,
val city: String?,
val hostname: String?
-)
+) : Parcelable
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt
index 998167468d..55757e5b8f 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt
@@ -3,16 +3,31 @@ package net.mullvad.mullvadvpn.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
-sealed class LocationConstraint(val code: Array<String>) : Parcelable {
+sealed class LocationConstraint : Parcelable {
+ abstract val location: GeoIpLocation
+
@Parcelize
- data class Country(val countryCode: String) :
- LocationConstraint(arrayOf(countryCode)), Parcelable
+ data class Country(val countryCode: String) : LocationConstraint(), Parcelable {
+ override val location: GeoIpLocation
+ get() = GeoIpLocation(null, null, countryCode, null, null)
+ }
@Parcelize
- data class City(val countryCode: String, val cityCode: String) :
- LocationConstraint(arrayOf(countryCode, cityCode)), Parcelable
+ data class City(
+ val countryCode: String,
+ val cityCode: String
+ ) : LocationConstraint(), Parcelable {
+ override val location: GeoIpLocation
+ get() = GeoIpLocation(null, null, countryCode, cityCode, null)
+ }
@Parcelize
- data class Hostname(val countryCode: String, val cityCode: String, val hostname: String) :
- LocationConstraint(arrayOf(countryCode, cityCode, hostname)), Parcelable
+ data class Hostname(
+ val countryCode: String,
+ val cityCode: String,
+ val hostname: String
+ ) : LocationConstraint(), Parcelable {
+ override val location: GeoIpLocation
+ get() = GeoIpLocation(null, null, countryCode, cityCode, hostname)
+ }
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/LocationInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/LocationInfoCache.kt
deleted file mode 100644
index bd45e78ce5..0000000000
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/LocationInfoCache.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.ReceiveChannel
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.sendBlocking
-import kotlinx.coroutines.withTimeout
-import net.mullvad.mullvadvpn.model.GeoIpLocation
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.relaylist.Relay
-import net.mullvad.mullvadvpn.relaylist.RelayCity
-import net.mullvad.mullvadvpn.relaylist.RelayCountry
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.util.ExponentialBackoff
-import net.mullvad.talpid.ConnectivityListener
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-
-class LocationInfoCache(
- val daemon: MullvadDaemon,
- val connectionProxy: ConnectionProxy,
- val connectivityListener: ConnectivityListener
-) {
- companion object {
- private enum class RequestFetch {
- ForRealLocation,
- ForRelayLocation,
- }
- }
-
- private val fetchRequestChannel = runFetcher()
-
- private var lastKnownRealLocation: GeoIpLocation? = null
- private var selectedRelayLocation: GeoIpLocation? = null
-
- var onNewLocation by observable<((GeoIpLocation?) -> Unit)?>(null) { _, _, callback ->
- callback?.invoke(location)
- }
-
- var location: GeoIpLocation? by observable(null) { _, _, newLocation ->
- onNewLocation?.invoke(newLocation)
- }
-
- var state by observable<TunnelState>(TunnelState.Disconnected) { _, _, newState ->
- when (newState) {
- is TunnelState.Disconnected -> {
- location = lastKnownRealLocation
- fetchRequestChannel.sendBlocking(RequestFetch.ForRealLocation)
- }
- is TunnelState.Connecting -> location = newState.location
- is TunnelState.Connected -> {
- location = newState.location
- fetchRequestChannel.sendBlocking(RequestFetch.ForRelayLocation)
- }
- is TunnelState.Disconnecting -> {
- when (newState.actionAfterDisconnect) {
- ActionAfterDisconnect.Nothing -> location = lastKnownRealLocation
- ActionAfterDisconnect.Block -> location = null
- ActionAfterDisconnect.Reconnect -> location = selectedRelayLocation
- }
- }
- is TunnelState.Error -> location = null
- }
- }
-
- var selectedRelay by observable<RelayItem?>(null) { _, oldRelay, newRelay ->
- if (newRelay != oldRelay) {
- updateSelectedRelayLocation(newRelay)
- }
- }
-
- init {
- connectivityListener.connectivityNotifier.subscribe(this) { isConnected ->
- if (isConnected && state is TunnelState.Disconnected) {
- fetchRequestChannel.sendBlocking(RequestFetch.ForRealLocation)
- }
- }
-
- connectionProxy.onStateChange.subscribe(this) { realState ->
- state = realState
- }
- }
-
- fun onDestroy() {
- connectivityListener.connectivityNotifier.unsubscribe(this)
- connectionProxy.onStateChange.unsubscribe(this)
- fetchRequestChannel.close()
- }
-
- private fun updateSelectedRelayLocation(relayItem: RelayItem?) {
- selectedRelayLocation = when (relayItem) {
- is RelayCountry -> GeoIpLocation(null, null, relayItem.name, null, null)
- is RelayCity -> GeoIpLocation(
- null,
- null,
- relayItem.country.name,
- relayItem.name,
- null
- )
- is Relay -> GeoIpLocation(
- null,
- null,
- relayItem.city.country.name,
- relayItem.city.name,
- relayItem.name
- )
- else -> null
- }
- }
-
- private fun runFetcher() = GlobalScope.actor<RequestFetch>(
- Dispatchers.Default,
- Channel.CONFLATED
- ) {
- try {
- fetcherLoop(channel)
- } catch (exception: ClosedReceiveChannelException) {
- }
- }
-
- private suspend fun fetcherLoop(channel: ReceiveChannel<RequestFetch>) {
- val delays = ExponentialBackoff().apply {
- scale = 50
- cap = 30 /* min */ * 60 /* s */ * 1000 /* ms */
- count = 17 // ceil(log2(cap / scale) + 1)
- }
-
- while (true) {
- var fetchType = channel.receive()
- var newLocation = daemon.getCurrentLocation()
-
- while (newLocation == null || !channel.isEmpty) {
- fetchType = delayOrReceive(delays, channel, fetchType)
- newLocation = daemon.getCurrentLocation()
- }
-
- handleNewLocation(newLocation, fetchType)
- delays.reset()
- }
- }
-
- private suspend fun delayOrReceive(
- delays: ExponentialBackoff,
- channel: ReceiveChannel<RequestFetch>,
- currentValue: RequestFetch
- ): RequestFetch {
- try {
- val newValue = withTimeout(delays.next()) {
- channel.receive()
- }
-
- delays.reset()
-
- return newValue
- } catch (timeOut: TimeoutCancellationException) {
- return currentValue
- }
- }
-
- private fun handleNewLocation(newLocation: GeoIpLocation, fetchType: RequestFetch) {
- if (fetchType == RequestFetch.ForRealLocation) {
- lastKnownRealLocation = newLocation
- }
-
- location = newLocation
- }
-}
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 46719f9806..91bfc8118b 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -105,7 +105,11 @@ class MullvadVpnService : TalpidVpnService() {
notificationManager = ForegroundNotificationManager(this, serviceNotifier, keyguardManager)
tunnelStateUpdater = TunnelStateUpdater(this, serviceNotifier)
- endpoint = ServiceEndpoint(Looper.getMainLooper(), daemonInstance.intermittentDaemon)
+ endpoint = ServiceEndpoint(
+ Looper.getMainLooper(),
+ daemonInstance.intermittentDaemon,
+ connectivityListener
+ )
notificationManager.acknowledgeStartForegroundService()
@@ -243,12 +247,14 @@ class MullvadVpnService : TalpidVpnService() {
handlePendingAction(connectionProxy, settings)
+ endpoint.locationInfoCache.stateEvents = connectionProxy.onStateChange
+
if (state == State.Running) {
instance = ServiceInstance(
endpoint.messenger,
daemon,
+ daemonInstance.intermittentDaemon,
connectionProxy,
- connectivityListener,
customDns,
endpoint.settingsListener,
splitTunneling
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 a124acfa64..b09a80696e 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ServiceInstance.kt
@@ -2,26 +2,24 @@ package net.mullvad.mullvadvpn.service
import android.os.Messenger
import net.mullvad.mullvadvpn.service.endpoint.SettingsListener
-import net.mullvad.talpid.ConnectivityListener
+import net.mullvad.mullvadvpn.util.Intermittent
class ServiceInstance(
val messenger: Messenger,
val daemon: MullvadDaemon,
+ val intermittentDaemon: Intermittent<MullvadDaemon>,
val connectionProxy: ConnectionProxy,
- val connectivityListener: ConnectivityListener,
val customDns: CustomDns,
val settingsListener: SettingsListener,
val splitTunneling: SplitTunneling
) {
val accountCache = AccountCache(daemon, settingsListener)
val keyStatusListener = KeyStatusListener(daemon)
- val locationInfoCache = LocationInfoCache(daemon, connectionProxy, connectivityListener)
fun onDestroy() {
accountCache.onDestroy()
connectionProxy.onDestroy()
customDns.onDestroy()
keyStatusListener.onDestroy()
- locationInfoCache.onDestroy()
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
new file mode 100644
index 0000000000..2f118ede6d
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
@@ -0,0 +1,140 @@
+package net.mullvad.mullvadvpn.service.endpoint
+
+import kotlin.properties.Delegates.observable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ClosedReceiveChannelException
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.actor
+import kotlinx.coroutines.channels.sendBlocking
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.receiveAsFlow
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.GeoIpLocation
+import net.mullvad.mullvadvpn.model.RelaySettings
+import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.util.ExponentialBackoff
+import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+import net.mullvad.talpid.util.autoSubscribable
+
+class LocationInfoCache(private val endpoint: ServiceEndpoint) {
+ companion object {
+ private enum class RequestFetch {
+ ForRealLocation,
+ ForRelayLocation,
+ }
+ }
+
+ private val fetchRetryDelays = ExponentialBackoff().apply {
+ scale = 50
+ cap = 30 /* min */ * 60 /* s */ * 1000 /* ms */
+ count = 17 // ceil(log2(cap / scale) + 1)
+ }
+
+ private val fetchRequestChannel = runFetcher()
+
+ private val daemon
+ get() = endpoint.intermittentDaemon
+
+ private var lastKnownRealLocation: GeoIpLocation? = null
+ private var selectedRelayLocation: GeoIpLocation? = null
+
+ var location: GeoIpLocation? by observable(null) { _, _, newLocation ->
+ endpoint.sendEvent(Event.NewLocation(newLocation))
+ }
+
+ var state by observable<TunnelState>(TunnelState.Disconnected) { _, _, newState ->
+ when (newState) {
+ is TunnelState.Disconnected -> {
+ location = lastKnownRealLocation
+ fetchRequestChannel.sendBlocking(RequestFetch.ForRealLocation)
+ }
+ is TunnelState.Connecting -> location = newState.location
+ is TunnelState.Connected -> {
+ location = newState.location
+ fetchRequestChannel.sendBlocking(RequestFetch.ForRelayLocation)
+ }
+ is TunnelState.Disconnecting -> {
+ when (newState.actionAfterDisconnect) {
+ ActionAfterDisconnect.Nothing -> location = lastKnownRealLocation
+ ActionAfterDisconnect.Block -> location = null
+ ActionAfterDisconnect.Reconnect -> location = selectedRelayLocation
+ }
+ }
+ is TunnelState.Error -> location = null
+ }
+ }
+
+ var stateEvents by autoSubscribable<TunnelState>(this, TunnelState.Disconnected) { newState ->
+ state = newState
+ }
+
+ init {
+ endpoint.connectivityListener.connectivityNotifier.subscribe(this) { isConnected ->
+ if (isConnected && state is TunnelState.Disconnected) {
+ fetchRequestChannel.sendBlocking(RequestFetch.ForRealLocation)
+ }
+ }
+
+ endpoint.settingsListener.relaySettingsNotifier.subscribe(this, ::updateSelectedLocation)
+ }
+
+ fun onDestroy() {
+ endpoint.connectivityListener.connectivityNotifier.unsubscribe(this)
+ endpoint.settingsListener.relaySettingsNotifier.unsubscribe(this)
+ stateEvents = null
+
+ fetchRequestChannel.close()
+ }
+
+ private fun runFetcher() = GlobalScope.actor<RequestFetch>(
+ Dispatchers.Default,
+ Channel.CONFLATED
+ ) {
+ try {
+ fetcherLoop(channel)
+ } catch (exception: ClosedReceiveChannelException) {
+ }
+ }
+
+ private suspend fun fetcherLoop(channel: ReceiveChannel<RequestFetch>) {
+ channel.receiveAsFlow()
+ .flatMapLatest(::fetchCurrentLocation)
+ .collect(::handleFetchedLocation)
+ }
+
+ private fun fetchCurrentLocation(fetchType: RequestFetch) = flow {
+ var newLocation = daemon.await().getCurrentLocation()
+
+ fetchRetryDelays.reset()
+
+ while (newLocation == null) {
+ delay(fetchRetryDelays.next())
+ newLocation = daemon.await().getCurrentLocation()
+ }
+
+ emit(Pair(newLocation, fetchType))
+ }
+
+ private suspend fun handleFetchedLocation(pairItem: Pair<GeoIpLocation, RequestFetch>) {
+ val (newLocation, fetchType) = pairItem
+
+ if (fetchType == RequestFetch.ForRealLocation) {
+ lastKnownRealLocation = newLocation
+ }
+
+ location = newLocation
+ }
+
+ private fun updateSelectedLocation(relaySettings: RelaySettings?) {
+ val settings = relaySettings as? RelaySettings.Normal
+ val constraint = settings?.relayConstraints?.location as? Constraint.Only
+
+ selectedRelayLocation = constraint?.value?.location
+ }
+}
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
index b9258c50ac..5f0ecc5ecd 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
@@ -15,10 +15,12 @@ import net.mullvad.mullvadvpn.ipc.Event
import net.mullvad.mullvadvpn.ipc.Request
import net.mullvad.mullvadvpn.service.MullvadDaemon
import net.mullvad.mullvadvpn.util.Intermittent
+import net.mullvad.talpid.ConnectivityListener
class ServiceEndpoint(
looper: Looper,
- internal val intermittentDaemon: Intermittent<MullvadDaemon>
+ internal val intermittentDaemon: Intermittent<MullvadDaemon>,
+ val connectivityListener: ConnectivityListener
) {
private val listeners = mutableSetOf<Messenger>()
private val registrationQueue: SendChannel<Messenger> = startRegistrator()
@@ -31,6 +33,8 @@ class ServiceEndpoint(
val settingsListener = SettingsListener(this)
+ val locationInfoCache = LocationInfoCache(this)
+
init {
dispatcher.registerHandler(Request.RegisterListener::class) { request ->
registrationQueue.sendBlocking(request.listener)
@@ -41,6 +45,7 @@ class ServiceEndpoint(
dispatcher.onDestroy()
registrationQueue.close()
+ locationInfoCache.onDestroy()
settingsListener.onDestroy()
}
@@ -83,6 +88,7 @@ class ServiceEndpoint(
val initialEvents = listOf(
Event.SettingsUpdate(settingsListener.settings),
+ Event.NewLocation(locationInfoCache.location),
Event.ListenerReady
)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
index 755a6c5fea..9c8e797945 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
@@ -91,7 +91,6 @@ class ConnectFragment :
relayListListener.onRelayListChange = { _, selectedRelayItem ->
jobTracker.newUiJob("updateSelectedRelayItem") {
- locationInfoCache.selectedRelay = selectedRelayItem
switchLocationButton.location = selectedRelayItem
}
}
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 6ff8b8c488..5e5ecba072 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
@@ -11,9 +11,9 @@ import net.mullvad.mullvadvpn.service.AccountCache
import net.mullvad.mullvadvpn.service.ConnectionProxy
import net.mullvad.mullvadvpn.service.CustomDns
import net.mullvad.mullvadvpn.service.KeyStatusListener
-import net.mullvad.mullvadvpn.service.LocationInfoCache
import net.mullvad.mullvadvpn.service.MullvadDaemon
import net.mullvad.mullvadvpn.service.SplitTunneling
+import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
import net.mullvad.mullvadvpn.ui.serviceconnection.SettingsListener
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt
new file mode 100644
index 0000000000..eb3a8c736a
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt
@@ -0,0 +1,26 @@
+package net.mullvad.mullvadvpn.ui.serviceconnection
+
+import kotlin.properties.Delegates.observable
+import net.mullvad.mullvadvpn.ipc.DispatchingHandler
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.model.GeoIpLocation
+
+class LocationInfoCache(val eventDispatcher: DispatchingHandler<Event>) {
+ private var location: GeoIpLocation? by observable(null) { _, _, newLocation ->
+ onNewLocation?.invoke(newLocation)
+ }
+
+ var onNewLocation by observable<((GeoIpLocation?) -> Unit)?>(null) { _, _, callback ->
+ callback?.invoke(location)
+ }
+
+ init {
+ eventDispatcher.registerHandler(Event.NewLocation::class) { event ->
+ location = event.location
+ }
+ }
+
+ fun onDestroy() {
+ onNewLocation = null
+ }
+}
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
index 9bc22dd069..da0e09b703 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt
@@ -26,7 +26,7 @@ class ServiceConnection(private val service: ServiceInstance, val mainActivity:
val connectionProxy = service.connectionProxy
val customDns = service.customDns
val keyStatusListener = service.keyStatusListener
- val locationInfoCache = service.locationInfoCache
+ val locationInfoCache = LocationInfoCache(dispatcher)
val settingsListener = SettingsListener(dispatcher)
val splitTunneling = service.splitTunneling
@@ -42,6 +42,7 @@ class ServiceConnection(private val service: ServiceInstance, val mainActivity:
fun onDestroy() {
dispatcher.onDestroy()
+ locationInfoCache.onDestroy()
settingsListener.onDestroy()
appVersionInfoCache.onDestroy()