summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-07-28 11:17:05 +0200
committerAlbin <albin@mullvad.net>2023-07-28 11:17:05 +0200
commit6ea45204b49571fea6e8cbbf2a3f49b2818278fd (patch)
tree606b8625cbf4454f7ecbf659d77ec7a26c14a113 /android/app
parent7a1c9dba446651f06b00c80178407b34120cede9 (diff)
parent463fca04ab97e09daa9d1de7ee7ffdebf93ad023 (diff)
downloadmullvadvpn-6ea45204b49571fea6e8cbbf2a3f49b2818278fd.tar.xz
mullvadvpn-6ea45204b49571fea6e8cbbf2a3f49b2818278fd.zip
Merge branch 'move-vpn-service-to-its-own-module'
Diffstat (limited to 'android/app')
-rw-r--r--android/app/build.gradle.kts1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildTypes.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/VpnServiceModule.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt85
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt117
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt295
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt275
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt180
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AppVersionInfoCache.kt56
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AuthTokenCache.kt49
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt85
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/CustomDns.kt133
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/DaemonDeviceDataSource.kt62
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt136
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayListListener.kt108
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt167
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SettingsListener.kt139
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SplitTunneling.kt59
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VoucherRedeemer.kt40
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VpnPermission.kt45
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt142
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt97
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt142
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt58
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/persistence/SplitTunnelingPersistence.kt37
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt41
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AccountFragment.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/LoginFragment.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/OutOfTimeFragment.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ViewLogsFragment.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/WelcomeFragment.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/InAppNotification.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/VersionInfoNotification.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/CustomDns.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSource.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/Button.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/RedeemVoucherButton.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Debouncer.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt21
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt30
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt44
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Intermittent.kt86
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt91
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/LocationConstraintExtensions.kt23
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt40
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt29
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt (renamed from android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt)3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt (renamed from android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt)5
61 files changed, 41 insertions, 2967 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 5d5144dcd1..1d467ddccd 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -208,6 +208,7 @@ play {
}
dependencies {
+ implementation(project(Dependencies.Mullvad.vpnService))
implementation(project(Dependencies.Mullvad.tileService))
implementation(project(Dependencies.Mullvad.commonLib))
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
index 25b2a187ad..80ffa2d1cd 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
@@ -28,8 +28,8 @@ import androidx.compose.ui.unit.sp
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.HtmlText
import net.mullvad.mullvadvpn.compose.component.textResource
+import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.model.Device
-import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
@Composable
fun ShowDeviceRemovalDialog(onDismiss: () -> Unit, onConfirm: () -> Unit, device: Device) {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt
index 4ec1792f48..ef68452ecf 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt
@@ -39,10 +39,10 @@ import net.mullvad.mullvadvpn.compose.theme.MullvadGreen
import net.mullvad.mullvadvpn.compose.theme.MullvadGreen40
import net.mullvad.mullvadvpn.compose.theme.MullvadWhite
import net.mullvad.mullvadvpn.compose.theme.MullvadWhite80
+import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
+import net.mullvad.mullvadvpn.lib.common.util.parseAsDateTime
import net.mullvad.mullvadvpn.model.Device
-import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.util.formatDate
-import net.mullvad.mullvadvpn.util.parseAsDateTime
@Composable
@Preview
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
index d0b460633b..c03d4cf723 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
@@ -35,9 +35,9 @@ import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
import net.mullvad.mullvadvpn.compose.state.SettingsUiState
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
import net.mullvad.mullvadvpn.compose.theme.Dimens
-import net.mullvad.mullvadvpn.constant.BuildTypes
-import net.mullvad.mullvadvpn.ui.extension.openLink
-import net.mullvad.mullvadvpn.util.appendHideNavOnReleaseBuild
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild
+import net.mullvad.mullvadvpn.lib.common.util.openLink
@OptIn(ExperimentalMaterial3Api::class)
@Preview
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildTypes.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildTypes.kt
deleted file mode 100644
index 0a85fdce2e..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildTypes.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.mullvadvpn.constant
-
-object BuildTypes {
- const val DEBUG = "debug"
- const val RELEASE = "release"
- const val FDROID = "fdroid"
- const val LEAK_CANARY = "leakCanary"
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/VpnServiceModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/VpnServiceModule.kt
deleted file mode 100644
index 431023caa2..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/VpnServiceModule.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.di
-
-import androidx.core.app.NotificationManagerCompat
-import org.koin.android.ext.koin.androidContext
-import org.koin.dsl.module
-
-val vpnServiceModule = module { single { NotificationManagerCompat.from(androidContext()) } }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt
deleted file mode 100644
index fcab12c6c9..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import kotlin.properties.Delegates.observable
-import kotlin.reflect.KClass
-import kotlin.reflect.safeCast
-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.trySendBlocking
-import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
-import net.mullvad.mullvadvpn.util.Intermittent
-
-class DaemonInstance(private val vpnService: MullvadVpnService) {
- sealed class Command {
- data class Start(val apiEndpointConfiguration: ApiEndpointConfiguration) : Command()
- object Stop : Command()
- }
-
- private val commandChannel = spawnActor()
-
- private var daemon by
- observable<MullvadDaemon?>(null) { _, oldInstance, _ -> oldInstance?.onDestroy() }
-
- val intermittentDaemon = Intermittent<MullvadDaemon>()
-
- fun start(apiEndpointConfiguration: ApiEndpointConfiguration) {
- commandChannel.trySendBlocking(Command.Start(apiEndpointConfiguration))
- }
-
- fun stop() {
- commandChannel.trySendBlocking(Command.Stop)
- }
-
- fun onDestroy() {
- commandChannel.close()
- intermittentDaemon.onDestroy()
- }
-
- private fun spawnActor() =
- GlobalScope.actor(Dispatchers.Default, Channel.UNLIMITED) {
- var isRunning = true
-
- while (isRunning) {
- val startCommand = waitForCommand(channel, Command.Start::class) ?: break
- startDaemon(startCommand.apiEndpointConfiguration)
- isRunning = waitForCommand(channel, Command.Stop::class) is Command.Stop
- stopDaemon()
- }
- }
-
- private suspend fun <T : Command> waitForCommand(
- channel: ReceiveChannel<Command>,
- command: KClass<T>
- ): T? {
- return try {
- var receivedCommand: T?
- do {
- receivedCommand = command.safeCast(channel.receive())
- } while (receivedCommand == null)
- receivedCommand
- } catch (exception: ClosedReceiveChannelException) {
- null
- }
- }
-
- private suspend fun startDaemon(apiEndpointConfiguration: ApiEndpointConfiguration) {
- val newDaemon =
- MullvadDaemon(vpnService, apiEndpointConfiguration).apply {
- onDaemonStopped = {
- intermittentDaemon.spawnUpdate(null)
- daemon = null
- }
- }
-
- daemon = newDaemon
- intermittentDaemon.update(newDaemon)
- }
-
- private fun stopDaemon() {
- daemon?.shutdown()
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
deleted file mode 100644
index 8fb7108619..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import android.app.Service
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.onStart
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.service.endpoint.ConnectionProxy
-import net.mullvad.mullvadvpn.service.notifications.TunnelStateNotification
-import net.mullvad.mullvadvpn.util.Intermittent
-import net.mullvad.mullvadvpn.util.JobTracker
-
-class ForegroundNotificationManager(
- val service: MullvadVpnService,
- val connectionProxy: ConnectionProxy,
- val intermittentDaemon: Intermittent<MullvadDaemon>
-) {
- private sealed class UpdaterMessage {
- class UpdateNotification : UpdaterMessage()
- class UpdateAction : UpdaterMessage()
- class NewTunnelState(val newState: TunnelState) : UpdaterMessage()
- }
-
- private val jobTracker = JobTracker()
- private val updater = runUpdater()
-
- private val tunnelStateNotification = TunnelStateNotification(service)
-
- private var loggedIn by
- observable(false) { _, _, _ -> updater.trySendBlocking(UpdaterMessage.UpdateAction()) }
-
- private val tunnelState
- get() = connectionProxy.onStateChange.latestEvent
-
- private val shouldBeOnForeground
- get() = lockedToForeground || !(tunnelState is TunnelState.Disconnected)
-
- var onForeground = false
- private set
-
- var lockedToForeground by
- observable(false) { _, _, _ ->
- updater.trySendBlocking(UpdaterMessage.UpdateNotification())
- }
-
- init {
- connectionProxy.onStateChange.subscribe(this) { newState ->
- updater.trySendBlocking(UpdaterMessage.NewTunnelState(newState))
- }
-
- intermittentDaemon.registerListener(this) { daemon ->
- jobTracker.newBackgroundJob("notificationLoggedInJob") {
- daemon
- ?.deviceStateUpdates
- ?.onStart { emit(daemon.getAndEmitDeviceState()) }
- ?.collect { deviceState -> loggedIn = deviceState is DeviceState.LoggedIn }
- }
- }
-
- updater.trySendBlocking(UpdaterMessage.UpdateNotification())
- }
-
- fun onDestroy() {
- jobTracker.cancelAllJobs()
- intermittentDaemon.unregisterListener(this)
- connectionProxy.onStateChange.unsubscribe(this)
- updater.close()
- }
-
- private fun runUpdater() =
- GlobalScope.actor<UpdaterMessage>(Dispatchers.Main, Channel.UNLIMITED) {
- for (message in channel) {
- when (message) {
- is UpdaterMessage.UpdateNotification -> updateNotification()
- is UpdaterMessage.UpdateAction -> updateNotificationAction()
- is UpdaterMessage.NewTunnelState -> {
- tunnelStateNotification.tunnelState = message.newState
- updateNotification()
- }
- }
- }
- }
-
- fun showOnForeground() {
- service.startForeground(
- TunnelStateNotification.NOTIFICATION_ID,
- tunnelStateNotification.build()
- )
-
- onForeground = true
- }
-
- fun updateNotification() {
- if (shouldBeOnForeground != onForeground) {
- if (shouldBeOnForeground) {
- showOnForeground()
- } else {
- service.stopForeground(Service.STOP_FOREGROUND_DETACH)
- onForeground = false
- }
- }
- }
-
- fun cancelNotification() {
- tunnelStateNotification.visible = false
- }
-
- private fun updateNotificationAction() {
- tunnelStateNotification.showAction = loggedIn
- }
-}
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
deleted file mode 100644
index 089e13ef31..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
+++ /dev/null
@@ -1,295 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpoint
-import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
-import net.mullvad.mullvadvpn.model.AppVersionInfo
-import net.mullvad.mullvadvpn.model.Device
-import net.mullvad.mullvadvpn.model.DeviceEvent
-import net.mullvad.mullvadvpn.model.DeviceListEvent
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.DnsOptions
-import net.mullvad.mullvadvpn.model.GeoIpLocation
-import net.mullvad.mullvadvpn.model.GetAccountDataResult
-import net.mullvad.mullvadvpn.model.LoginResult
-import net.mullvad.mullvadvpn.model.ObfuscationSettings
-import net.mullvad.mullvadvpn.model.QuantumResistantState
-import net.mullvad.mullvadvpn.model.RelayList
-import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
-import net.mullvad.mullvadvpn.model.RemoveDeviceEvent
-import net.mullvad.mullvadvpn.model.RemoveDeviceResult
-import net.mullvad.mullvadvpn.model.Settings
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
-import net.mullvad.talpid.util.EventNotifier
-
-class MullvadDaemon(
- vpnService: MullvadVpnService,
- apiEndpointConfiguration: ApiEndpointConfiguration
-) {
- protected var daemonInterfaceAddress = 0L
-
- val onSettingsChange = EventNotifier<Settings?>(null)
- var onTunnelStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
-
- var onAppVersionInfoChange: ((AppVersionInfo) -> Unit)? = null
- var onRelayListChange: ((RelayList) -> Unit)? = null
- var onDaemonStopped: (() -> Unit)? = null
-
- private val _deviceStateUpdates = MutableSharedFlow<DeviceState>(extraBufferCapacity = 1)
- val deviceStateUpdates = _deviceStateUpdates.asSharedFlow()
-
- private val _deviceListUpdates = MutableSharedFlow<DeviceListEvent>(extraBufferCapacity = 1)
- val deviceListUpdates = _deviceListUpdates.asSharedFlow()
-
- init {
- System.loadLibrary("mullvad_jni")
-
- initialize(
- vpnService = vpnService,
- cacheDirectory = vpnService.cacheDir.absolutePath,
- resourceDirectory = vpnService.filesDir.absolutePath,
- apiEndpoint = apiEndpointConfiguration.apiEndpoint()
- )
-
- onSettingsChange.notify(getSettings())
-
- onTunnelStateChange.notify(getState() ?: TunnelState.Disconnected)
- }
-
- fun connect() {
- connect(daemonInterfaceAddress)
- }
-
- fun createNewAccount(): String? {
- return createNewAccount(daemonInterfaceAddress)
- }
-
- fun disconnect() {
- disconnect(daemonInterfaceAddress)
- }
-
- fun getAccountData(accountToken: String): GetAccountDataResult {
- return getAccountData(daemonInterfaceAddress, accountToken)
- }
-
- fun getAccountHistory(): String? {
- return getAccountHistory(daemonInterfaceAddress)
- }
-
- fun getWwwAuthToken(): String {
- return getWwwAuthToken(daemonInterfaceAddress) ?: ""
- }
-
- fun getCurrentLocation(): GeoIpLocation? {
- return getCurrentLocation(daemonInterfaceAddress)
- }
-
- fun getCurrentVersion(): String? {
- return getCurrentVersion(daemonInterfaceAddress)
- }
-
- fun getRelayLocations(): RelayList? {
- return getRelayLocations(daemonInterfaceAddress)
- }
-
- fun getSettings(): Settings? {
- return getSettings(daemonInterfaceAddress)
- }
-
- fun getState(): TunnelState? {
- return getState(daemonInterfaceAddress)
- }
-
- fun getVersionInfo(): AppVersionInfo? {
- return getVersionInfo(daemonInterfaceAddress)
- }
-
- fun reconnect() {
- reconnect(daemonInterfaceAddress)
- }
-
- fun clearAccountHistory() {
- clearAccountHistory(daemonInterfaceAddress)
- }
-
- fun loginAccount(accountToken: String): LoginResult {
- return loginAccount(daemonInterfaceAddress, accountToken)
- }
-
- fun logoutAccount() = logoutAccount(daemonInterfaceAddress)
-
- fun getAndEmitDeviceList(accountToken: String): List<Device>? {
- return listDevices(daemonInterfaceAddress, accountToken).also { deviceList ->
- _deviceListUpdates.tryEmit(
- if (deviceList == null) {
- DeviceListEvent.Error
- } else {
- DeviceListEvent.Available(accountToken, deviceList)
- }
- )
- }
- }
-
- fun getAndEmitDeviceState(): DeviceState {
- return getDevice(daemonInterfaceAddress).also { deviceState ->
- _deviceStateUpdates.tryEmit(deviceState)
- }
- }
-
- fun refreshDevice() {
- updateDevice(daemonInterfaceAddress)
- getAndEmitDeviceState()
- }
-
- fun removeDevice(accountToken: String, deviceId: String): RemoveDeviceResult {
- return removeDevice(daemonInterfaceAddress, accountToken, deviceId)
- }
-
- fun setAllowLan(allowLan: Boolean) {
- setAllowLan(daemonInterfaceAddress, allowLan)
- }
-
- fun setAutoConnect(autoConnect: Boolean) {
- setAutoConnect(daemonInterfaceAddress, autoConnect)
- }
-
- fun setDnsOptions(dnsOptions: DnsOptions) {
- setDnsOptions(daemonInterfaceAddress, dnsOptions)
- }
-
- fun setWireguardMtu(wireguardMtu: Int?) {
- setWireguardMtu(daemonInterfaceAddress, wireguardMtu)
- }
-
- fun shutdown() {
- shutdown(daemonInterfaceAddress)
- }
-
- fun submitVoucher(voucher: String): VoucherSubmissionResult {
- return submitVoucher(daemonInterfaceAddress, voucher)
- }
-
- fun updateRelaySettings(update: RelaySettingsUpdate) {
- updateRelaySettings(daemonInterfaceAddress, update)
- }
-
- fun setObfuscationSettings(settings: ObfuscationSettings?) {
- setObfuscationSettings(daemonInterfaceAddress, settings)
- }
-
- fun setQuantumResistant(quantumResistant: QuantumResistantState) {
- setQuantumResistantTunnel(daemonInterfaceAddress, quantumResistant)
- }
-
- fun onDestroy() {
- onSettingsChange.unsubscribeAll()
- onTunnelStateChange.unsubscribeAll()
-
- onAppVersionInfoChange = null
- onRelayListChange = null
- onDaemonStopped = null
-
- deinitialize()
- }
-
- private external fun initialize(
- vpnService: MullvadVpnService,
- cacheDirectory: String,
- resourceDirectory: String,
- apiEndpoint: ApiEndpoint?
- )
-
- private external fun deinitialize()
-
- private external fun connect(daemonInterfaceAddress: Long)
- private external fun createNewAccount(daemonInterfaceAddress: Long): String?
- private external fun disconnect(daemonInterfaceAddress: Long)
- private external fun getAccountData(
- daemonInterfaceAddress: Long,
- accountToken: String
- ): GetAccountDataResult
-
- private external fun getAccountHistory(daemonInterfaceAddress: Long): String?
- private external fun getWwwAuthToken(daemonInterfaceAddress: Long): String?
- private external fun getCurrentLocation(daemonInterfaceAddress: Long): GeoIpLocation?
- private external fun getCurrentVersion(daemonInterfaceAddress: Long): String?
- private external fun getRelayLocations(daemonInterfaceAddress: Long): RelayList?
- private external fun getSettings(daemonInterfaceAddress: Long): Settings?
- private external fun getState(daemonInterfaceAddress: Long): TunnelState?
- private external fun getVersionInfo(daemonInterfaceAddress: Long): AppVersionInfo?
- private external fun reconnect(daemonInterfaceAddress: Long)
- private external fun clearAccountHistory(daemonInterfaceAddress: Long)
- private external fun loginAccount(
- daemonInterfaceAddress: Long,
- accountToken: String?
- ): LoginResult
-
- private external fun logoutAccount(daemonInterfaceAddress: Long)
- private external fun listDevices(
- daemonInterfaceAddress: Long,
- accountToken: String?
- ): List<Device>?
-
- private external fun getDevice(daemonInterfaceAddress: Long): DeviceState
- private external fun updateDevice(daemonInterfaceAddress: Long)
- private external fun removeDevice(
- daemonInterfaceAddress: Long,
- accountToken: String?,
- deviceId: String
- ): RemoveDeviceResult
-
- private external fun setAllowLan(daemonInterfaceAddress: Long, allowLan: Boolean)
- private external fun setAutoConnect(daemonInterfaceAddress: Long, alwaysOn: Boolean)
- private external fun setDnsOptions(daemonInterfaceAddress: Long, dnsOptions: DnsOptions)
- private external fun setWireguardMtu(daemonInterfaceAddress: Long, wireguardMtu: Int?)
- private external fun shutdown(daemonInterfaceAddress: Long)
- private external fun submitVoucher(
- daemonInterfaceAddress: Long,
- voucher: String
- ): VoucherSubmissionResult
-
- private external fun updateRelaySettings(
- daemonInterfaceAddress: Long,
- update: RelaySettingsUpdate
- )
-
- private external fun setObfuscationSettings(
- daemonInterfaceAddress: Long,
- settings: ObfuscationSettings?
- )
-
- private external fun setQuantumResistantTunnel(
- daemonInterfaceAddress: Long,
- quantumResistant: QuantumResistantState
- )
-
- private fun notifyAppVersionInfoEvent(appVersionInfo: AppVersionInfo) {
- onAppVersionInfoChange?.invoke(appVersionInfo)
- }
-
- private fun notifyRelayListEvent(relayList: RelayList) {
- onRelayListChange?.invoke(relayList)
- }
-
- private fun notifySettingsEvent(settings: Settings) {
- onSettingsChange.notify(settings)
- }
-
- private fun notifyTunnelStateEvent(event: TunnelState) {
- onTunnelStateChange.notify(event)
- }
-
- private fun notifyDaemonStopped() {
- onDaemonStopped?.invoke()
- }
-
- private fun notifyDeviceEvent(event: DeviceEvent) {
- _deviceStateUpdates.tryEmit(event.newState)
- }
-
- private fun notifyRemoveDeviceEvent(event: RemoveDeviceEvent) {
- _deviceListUpdates.tryEmit(DeviceListEvent.Available(event.accountToken, event.newDevices))
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
deleted file mode 100644
index 0058b09e4f..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ /dev/null
@@ -1,275 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.content.Intent
-import android.net.VpnService
-import android.os.IBinder
-import android.os.Looper
-import android.util.Log
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import net.mullvad.mullvadvpn.BuildConfig
-import net.mullvad.mullvadvpn.di.vpnServiceModule
-import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
-import net.mullvad.mullvadvpn.lib.endpoint.DefaultApiEndpointConfiguration
-import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras
-import net.mullvad.mullvadvpn.model.Settings
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.service.endpoint.ServiceEndpoint
-import net.mullvad.mullvadvpn.service.notifications.AccountExpiryNotification
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.talpid.TalpidVpnService
-import org.koin.core.context.loadKoinModules
-
-class MullvadVpnService : TalpidVpnService() {
- companion object {
- private val TAG = "mullvad"
-
- val KEY_CONNECT_ACTION = "net.mullvad.mullvadvpn.connect_action"
- val KEY_DISCONNECT_ACTION = "net.mullvad.mullvadvpn.disconnect_action"
- val KEY_QUIT_ACTION = "net.mullvad.mullvadvpn.quit_action"
-
- init {
- System.loadLibrary("mullvad_jni")
- }
- }
-
- private enum class PendingAction {
- Connect,
- Disconnect,
- }
-
- private enum class State {
- Running,
- Stopping,
- Stopped,
- }
-
- private val connectionProxy
- get() = endpoint.connectionProxy
-
- private var state = State.Running
-
- private var setUpDaemonJob: Job? = null
-
- private lateinit var accountExpiryNotification: AccountExpiryNotification
- private lateinit var daemonInstance: DaemonInstance
- private lateinit var endpoint: ServiceEndpoint
- private lateinit var keyguardManager: KeyguardManager
- private lateinit var notificationManager: ForegroundNotificationManager
-
- private var pendingAction by
- observable<PendingAction?>(null) { _, _, _ ->
- endpoint.settingsListener.settings?.let { settings -> handlePendingAction(settings) }
- }
-
- private var apiEndpointConfiguration: ApiEndpointConfiguration =
- DefaultApiEndpointConfiguration()
-
- override fun onCreate() {
- super.onCreate()
- Log.d(TAG, "Initializing service")
-
- loadKoinModules(vpnServiceModule)
-
- daemonInstance = DaemonInstance(this)
- keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
-
- endpoint =
- ServiceEndpoint(
- Looper.getMainLooper(),
- daemonInstance.intermittentDaemon,
- connectivityListener,
- this
- )
-
- endpoint.splitTunneling.onChange.subscribe(this@MullvadVpnService) { excludedApps ->
- disallowedApps = excludedApps
- markTunAsStale()
- connectionProxy.reconnect()
- }
-
- notificationManager =
- ForegroundNotificationManager(this, connectionProxy, daemonInstance.intermittentDaemon)
-
- accountExpiryNotification =
- AccountExpiryNotification(
- this,
- daemonInstance.intermittentDaemon,
- endpoint.accountCache
- )
-
- // Remove any leftover tunnel state persistence data
- getSharedPreferences("tunnel_state", MODE_PRIVATE).edit().clear().commit()
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- Log.d(TAG, "Starting service")
-
- if (BuildConfig.DEBUG) {
- intent?.getApiEndpointConfigurationExtras()?.let { apiEndpointConfiguration = it }
- }
-
- daemonInstance.apply {
- intermittentDaemon.registerListener(this@MullvadVpnService) { daemon ->
- handleDaemonInstance(daemon)
- }
-
- start(apiEndpointConfiguration)
- }
-
- val startResult = super.onStartCommand(intent, flags, startId)
- var quitCommand = false
-
- // Always promote to foreground if connect/disconnect actions are provided to mitigate cases
- // where the service would potentially otherwise be too slow running `startForeground`.
- if (intent?.action == KEY_CONNECT_ACTION || intent?.action == KEY_DISCONNECT_ACTION) {
- notificationManager.showOnForeground()
- }
-
- notificationManager.updateNotification()
-
- if (!keyguardManager.isDeviceLocked) {
- val action = intent?.action
-
- if (action == VpnService.SERVICE_INTERFACE || action == KEY_CONNECT_ACTION) {
- pendingAction = PendingAction.Connect
- } else if (action == KEY_DISCONNECT_ACTION) {
- pendingAction = PendingAction.Disconnect
- } else if (action == KEY_QUIT_ACTION && !notificationManager.onForeground) {
- quitCommand = true
- stop()
- }
- }
-
- if (state == State.Stopping && !quitCommand) {
- restart()
- }
-
- return startResult
- }
-
- override fun onBind(intent: Intent): IBinder {
- Log.d(TAG, "New connection to service")
- return super.onBind(intent) ?: endpoint.messenger.binder
- }
-
- override fun onRebind(intent: Intent) {
- Log.d(TAG, "Connection to service restored")
- if (state == State.Stopping) {
- restart()
- }
- }
-
- override fun onRevoke() {
- pendingAction = PendingAction.Disconnect
- }
-
- override fun onUnbind(intent: Intent): Boolean {
- Log.d(TAG, "Closed all connections to service")
-
- if (state != State.Running) {
- stop()
- }
-
- return true
- }
-
- override fun onDestroy() {
- Log.d(TAG, "Service has stopped")
- state = State.Stopped
- accountExpiryNotification.onDestroy()
- notificationManager.onDestroy()
- daemonInstance.onDestroy()
- super.onDestroy()
- }
-
- override fun onTaskRemoved(rootIntent: Intent?) {
- connectionProxy.onStateChange.latestEvent.let { tunnelState ->
- Log.d(TAG, "Task removed (tunnelState=$tunnelState)")
- if (tunnelState == TunnelState.Disconnected) {
- notificationManager.cancelNotification()
- stop()
- }
- }
- }
-
- private fun handleDaemonInstance(daemon: MullvadDaemon?) {
- setUpDaemonJob?.cancel()
-
- if (daemon != null) {
- setUpDaemonJob = setUpDaemon(daemon)
- } else {
- Log.d(TAG, "Daemon has stopped")
-
- if (state == State.Running) {
- restart()
- }
- }
- }
-
- private fun setUpDaemon(daemon: MullvadDaemon) =
- GlobalScope.launch(Dispatchers.Main) {
- if (state != State.Stopped) {
- val settings = daemon.getSettings()
-
- if (settings != null) {
- handlePendingAction(settings)
- } else {
- restart()
- }
- }
- }
-
- private fun stop() {
- Log.d(TAG, "Stopping service")
- state = State.Stopping
- daemonInstance.stop()
- stopSelf()
- }
-
- private fun restart() {
- if (state != State.Stopped) {
- Log.d(TAG, "Restarting service")
-
- state = State.Running
-
- daemonInstance.apply {
- stop()
- start(apiEndpointConfiguration)
- }
- } else {
- Log.d(TAG, "Ignoring restart because onDestroy has executed")
- }
- }
-
- private fun handlePendingAction(settings: Settings) {
- when (pendingAction) {
- PendingAction.Connect -> {
- if (settings != null) {
- connectionProxy.connect()
- } else {
- openUi()
- }
- }
- PendingAction.Disconnect -> connectionProxy.disconnect()
- null -> return
- }
-
- pendingAction = null
- }
-
- private fun openUi() {
- val intent =
- Intent(this, MainActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- }
-
- startActivity(intent)
- }
-}
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
deleted file mode 100644
index 01d8bcea83..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-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.GetAccountDataResult
-import net.mullvad.mullvadvpn.util.JobTracker
-import net.mullvad.mullvadvpn.util.parseAsDateTime
-import net.mullvad.talpid.util.EventNotifier
-
-class AccountCache(private val endpoint: ServiceEndpoint) {
- companion object {
- private sealed class Command {
- object CreateAccount : Command()
- data class Login(val account: String) : Command()
- object Logout : Command()
- }
- }
-
- private val commandChannel = spawnActor()
-
- private val daemon
- get() = endpoint.intermittentDaemon
-
- val onAccountExpiryChange = EventNotifier<AccountExpiry>(AccountExpiry.Missing)
- val onAccountHistoryChange = EventNotifier<AccountHistory>(AccountHistory.Missing)
-
- private val jobTracker = JobTracker()
-
- private var accountExpiry by onAccountExpiryChange.notifiable()
- private var accountHistory by onAccountHistoryChange.notifiable()
-
- private var cachedAccountToken: String? = null
- private var cachedCreatedAccountToken: String? = null
-
- val isNewAccount: Boolean
- get() = cachedAccountToken == cachedCreatedAccountToken
-
- init {
- jobTracker.newBackgroundJob("autoFetchAccountExpiry") {
- daemon.await().deviceStateUpdates.collect { deviceState ->
- accountExpiry =
- deviceState
- .token()
- .also { cachedAccountToken = it }
- ?.let { fetchAccountExpiry(it) }
- ?: AccountExpiry.Missing
- }
- }
-
- onAccountHistoryChange.subscribe(this) { history ->
- endpoint.sendEvent(Event.AccountHistoryEvent(history))
- }
-
- onAccountExpiryChange.subscribe(this) { endpoint.sendEvent(Event.AccountExpiryEvent(it)) }
-
- endpoint.dispatcher.apply {
- registerHandler(Request.CreateAccount::class) { _ ->
- commandChannel.trySendBlocking(Command.CreateAccount)
- }
-
- registerHandler(Request.Login::class) { request ->
- request.account?.let { account ->
- commandChannel.trySendBlocking(Command.Login(account))
- }
- }
-
- registerHandler(Request.Logout::class) { _ ->
- commandChannel.trySendBlocking(Command.Logout)
- }
-
- registerHandler(Request.FetchAccountExpiry::class) { _ ->
- jobTracker.newBackgroundJob("fetchAccountExpiry") {
- accountExpiry =
- cachedAccountToken?.let { fetchAccountExpiry(it) } ?: AccountExpiry.Missing
- }
- }
-
- registerHandler(Request.FetchAccountHistory::class) { _ ->
- jobTracker.newBackgroundJob("fetchAccountHistory") {
- accountHistory = fetchAccountHistory()
- }
- }
-
- registerHandler(Request.ClearAccountHistory::class) { _ ->
- jobTracker.newBackgroundJob("clearAccountHistory") { clearAccountHistory() }
- }
- }
- }
-
- fun onDestroy() {
- jobTracker.cancelAllJobs()
-
- onAccountExpiryChange.unsubscribeAll()
- onAccountHistoryChange.unsubscribeAll()
-
- commandChannel.close()
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- for (command in channel) {
- when (command) {
- is Command.CreateAccount -> doCreateAccount()
- is Command.Login -> doLogin(command.account)
- is Command.Logout -> doLogout()
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Command channel was closed, stop the actor
- }
- }
-
- private suspend fun clearAccountHistory() {
- daemon.await().clearAccountHistory()
- accountHistory = fetchAccountHistory()
- }
-
- private suspend fun doCreateAccount() {
- daemon
- .await()
- .createNewAccount()
- .also { newAccountToken -> cachedCreatedAccountToken = newAccountToken }
- .let { newAccountToken ->
- if (newAccountToken != null) {
- AccountCreationResult.Success(newAccountToken)
- } else {
- AccountCreationResult.Failure
- }
- }
- .also { result -> endpoint.sendEvent(Event.AccountCreationEvent(result)) }
- }
-
- private suspend fun doLogin(account: String) {
- daemon.await().loginAccount(account).also { result ->
- endpoint.sendEvent(Event.LoginEvent(result))
- }
- }
-
- private suspend fun doLogout() {
- daemon.await().logoutAccount()
- accountHistory = fetchAccountHistory()
- }
-
- private suspend fun fetchAccountHistory(): AccountHistory {
- return daemon.await().getAccountHistory().let { history ->
- if (history != null) {
- AccountHistory.Available(history)
- } else {
- AccountHistory.Missing
- }
- }
- }
-
- private suspend fun fetchAccountExpiry(accountToken: String): AccountExpiry {
- return fetchAccountData(accountToken).let { result ->
- if (result is GetAccountDataResult.Ok) {
- result.accountData.expiry.parseAsDateTime()?.let { parsedDateTime ->
- AccountExpiry.Available(parsedDateTime)
- }
- ?: AccountExpiry.Missing
- } else {
- AccountExpiry.Missing
- }
- }
- }
-
- private suspend fun fetchAccountData(accountToken: String): GetAccountDataResult {
- return daemon.await().getAccountData(accountToken)
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AppVersionInfoCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AppVersionInfoCache.kt
deleted file mode 100644
index 767ac3e251..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AppVersionInfoCache.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlin.properties.Delegates.observable
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.model.AppVersionInfo
-import net.mullvad.mullvadvpn.service.MullvadDaemon
-
-class AppVersionInfoCache(endpoint: ServiceEndpoint) {
- private val daemon = endpoint.intermittentDaemon
-
- var appVersionInfo by
- observable<AppVersionInfo?>(null) { _, _, info ->
- endpoint.sendEvent(Event.AppVersionInfo(info))
- }
- private set
-
- var currentVersion by
- observable<String?>(null) { _, _, version ->
- endpoint.sendEvent(Event.CurrentVersion(version))
- }
- private set
-
- init {
- daemon.registerListener(this) { newDaemon ->
- newDaemon?.let { daemon ->
- initializeCurrentVersion(daemon)
- registerVersionInfoListener(daemon)
- fetchInitialVersionInfo(daemon)
- }
- }
- }
-
- fun onDestroy() {
- daemon.unregisterListener(this)
- }
-
- private fun initializeCurrentVersion(daemon: MullvadDaemon) {
- if (currentVersion == null) {
- currentVersion = daemon.getCurrentVersion()
- }
- }
-
- private fun registerVersionInfoListener(daemon: MullvadDaemon) {
- daemon.onAppVersionInfoChange = { newAppVersionInfo ->
- synchronized(this@AppVersionInfoCache) { appVersionInfo = newAppVersionInfo }
- }
- }
-
- private fun fetchInitialVersionInfo(daemon: MullvadDaemon) {
- synchronized(this@AppVersionInfoCache) {
- if (appVersionInfo == null) {
- appVersionInfo = daemon.getVersionInfo()
- }
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AuthTokenCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AuthTokenCache.kt
deleted file mode 100644
index 6506c0469d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AuthTokenCache.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-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.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-
-class AuthTokenCache(endpoint: ServiceEndpoint) {
- companion object {
- private enum class Command {
- Fetch
- }
- }
-
- private val daemon = endpoint.intermittentDaemon
- private val requestQueue = spawnActor()
-
- var authToken by
- observable<String?>(null) { _, _, token -> endpoint.sendEvent(Event.AuthToken(token)) }
- private set
-
- init {
- endpoint.dispatcher.registerHandler(Request.FetchAuthToken::class) { _ ->
- requestQueue.trySendBlocking(Command.Fetch)
- }
- }
-
- fun onDestroy() {
- requestQueue.close()
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- for (command in channel) {
- when (command) {
- Command.Fetch -> authToken = daemon.await().getWwwAuthToken()
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Closed sender, so stop the actor
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt
deleted file mode 100644
index a2c97a05bd..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.talpid.util.EventNotifier
-
-class ConnectionProxy(val vpnPermission: VpnPermission, endpoint: ServiceEndpoint) {
- private enum class Command {
- CONNECT,
- RECONNECT,
- DISCONNECT,
- }
-
- private val commandChannel = spawnActor()
- private val daemon = endpoint.intermittentDaemon
- private val initialState = TunnelState.Disconnected
-
- var onStateChange = EventNotifier<TunnelState>(initialState)
-
- var state by onStateChange.notifiable()
- private set
-
- init {
- daemon.registerListener(this) { newDaemon ->
- newDaemon?.onTunnelStateChange?.subscribe(this@ConnectionProxy) { newState ->
- state = newState
- }
- }
-
- onStateChange.subscribe(this) { tunnelState ->
- endpoint.sendEvent(Event.TunnelStateChange(tunnelState))
- }
-
- endpoint.dispatcher.apply {
- registerHandler(Request.Connect::class) { _ -> connect() }
- registerHandler(Request.Reconnect::class) { _ -> reconnect() }
- registerHandler(Request.Disconnect::class) { _ -> disconnect() }
- }
- }
-
- fun connect() {
- commandChannel.trySendBlocking(Command.CONNECT)
- }
-
- fun reconnect() {
- commandChannel.trySendBlocking(Command.RECONNECT)
- }
-
- fun disconnect() {
- commandChannel.trySendBlocking(Command.DISCONNECT)
- }
-
- fun onDestroy() {
- commandChannel.close()
- onStateChange.unsubscribeAll()
- daemon.unregisterListener(this)
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- while (true) {
- val command = channel.receive()
-
- when (command) {
- Command.CONNECT -> {
- vpnPermission.request()
- daemon.await().connect()
- }
- Command.RECONNECT -> daemon.await().reconnect()
- Command.DISCONNECT -> daemon.await().disconnect()
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Closed sender, so stop the actor
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/CustomDns.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/CustomDns.kt
deleted file mode 100644
index fe8f55a66d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/CustomDns.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import java.net.InetAddress
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.model.CustomDnsOptions
-import net.mullvad.mullvadvpn.model.DefaultDnsOptions
-import net.mullvad.mullvadvpn.model.DnsOptions
-import net.mullvad.mullvadvpn.model.DnsState
-
-class CustomDns(private val endpoint: ServiceEndpoint) {
- private sealed class Command {
- @Deprecated("Use SetDnsOptions") class AddDnsServer(val server: InetAddress) : Command()
- @Deprecated("Use SetDnsOptions") class RemoveDnsServer(val server: InetAddress) : Command()
- @Deprecated("Use SetDnsOptions")
- class ReplaceDnsServer(val oldServer: InetAddress, val newServer: InetAddress) : Command()
- @Deprecated("Use SetDnsOptions") class SetEnabled(val enabled: Boolean) : Command()
-
- class SetDnsOptions(val dnsOptions: DnsOptions) : Command()
- }
-
- private val commandChannel = spawnActor()
- private val dnsServers = ArrayList<InetAddress>()
-
- private val daemon
- get() = endpoint.intermittentDaemon
-
- private var enabled = false
-
- init {
- endpoint.settingsListener.dnsOptionsNotifier.subscribe(this) { maybeDnsOptions ->
- maybeDnsOptions?.let { dnsOptions ->
- enabled = dnsOptions.state == DnsState.Custom
- dnsServers.clear()
- dnsServers.addAll(dnsOptions.customOptions.addresses)
- }
- }
-
- endpoint.dispatcher.apply {
- registerHandler(Request.AddCustomDnsServer::class) { request ->
- commandChannel.trySendBlocking(Command.AddDnsServer(request.address))
- }
-
- registerHandler(Request.RemoveCustomDnsServer::class) { request ->
- commandChannel.trySendBlocking(Command.RemoveDnsServer(request.address))
- }
-
- registerHandler(Request.ReplaceCustomDnsServer::class) { request ->
- commandChannel.trySendBlocking(
- Command.ReplaceDnsServer(request.oldAddress, request.newAddress)
- )
- }
-
- registerHandler(Request.SetEnableCustomDns::class) { request ->
- commandChannel.trySendBlocking(Command.SetEnabled(request.enable))
- }
-
- registerHandler(Request.SetDnsOptions::class) { request ->
- commandChannel.trySendBlocking(Command.SetDnsOptions(request.dnsOptions))
- }
- }
- }
-
- fun onDestroy() {
- endpoint.settingsListener.dnsOptionsNotifier.unsubscribe(this)
- commandChannel.close()
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- while (true) {
- val command = channel.receive()
-
- when (command) {
- is Command.AddDnsServer -> doAddDnsServer(command.server)
- is Command.RemoveDnsServer -> doRemoveDnsServer(command.server)
- is Command.ReplaceDnsServer -> {
- doReplaceDnsServer(command.oldServer, command.newServer)
- }
- is Command.SetEnabled -> changeDnsOptions(command.enabled)
- is Command.SetDnsOptions -> setDnsOptions(command.dnsOptions)
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Closed sender, so stop the actor
- }
- }
-
- private suspend fun doAddDnsServer(server: InetAddress) {
- if (!dnsServers.contains(server)) {
- dnsServers.add(server)
- changeDnsOptions(enabled)
- }
- }
-
- private suspend fun doReplaceDnsServer(oldServer: InetAddress, newServer: InetAddress) {
- if (oldServer != newServer && !dnsServers.contains(newServer)) {
- val index = dnsServers.indexOf(oldServer)
-
- if (index >= 0) {
- dnsServers.removeAt(index)
- dnsServers.add(index, newServer)
- changeDnsOptions(enabled)
- }
- }
- }
-
- private suspend fun doRemoveDnsServer(server: InetAddress) {
- if (dnsServers.remove(server)) {
- changeDnsOptions(enabled)
- }
- }
-
- private suspend fun changeDnsOptions(enable: Boolean) {
- val options =
- DnsOptions(
- state = if (enable) DnsState.Custom else DnsState.Default,
- customOptions = CustomDnsOptions(dnsServers),
- defaultOptions = DefaultDnsOptions()
- )
- daemon.await().setDnsOptions(options)
- }
-
- private suspend fun setDnsOptions(dnsOptions: DnsOptions) {
- daemon.await().setDnsOptions(dnsOptions)
- }
-}
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
deleted file mode 100644
index 5a0efc82fd..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/DaemonDeviceDataSource.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlinx.coroutines.flow.collect
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-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("propagateDeviceUpdatesJob") {
- daemon.deviceStateUpdates.collect { newState ->
- endpoint.sendEvent(Event.DeviceStateEvent(newState))
- }
- }
-
- tracker.newBackgroundJob("propagateDeviceListUpdatesJob") {
- daemon.deviceListUpdates.collect { newState ->
- endpoint.sendEvent(Event.DeviceListUpdate(newState))
- }
- }
-
- endpoint.dispatcher.registerHandler(Request.GetDevice::class) {
- tracker.newBackgroundJob("getDeviceJob") { daemon.getAndEmitDeviceState() }
- }
-
- endpoint.dispatcher.registerHandler(Request.RefreshDeviceState::class) {
- tracker.newBackgroundJob("refreshDeviceJob") { daemon.refreshDevice() }
- }
-
- endpoint.dispatcher.registerHandler(Request.RemoveDevice::class) { request ->
- tracker.newBackgroundJob("removeDeviceJob") {
- daemon.removeDevice(request.accountToken, request.deviceId).also { result ->
- endpoint.sendEvent(Event.DeviceRemovalEvent(request.deviceId, result))
- }
- }
- }
-
- endpoint.dispatcher.registerHandler(Request.GetDeviceList::class) { request ->
- tracker.newBackgroundJob("getDeviceListJob") {
- daemon.getAndEmitDeviceList(request.accountToken)
- }
- }
- }
-
- fun onDestroy() {
- tracker.cancelAllJobs()
- endpoint.intermittentDaemon.unregisterListener(this)
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
deleted file mode 100644
index 819ea10d77..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-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.trySendBlocking
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.receiveAsFlow
-import net.mullvad.mullvadvpn.lib.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.mullvadvpn.util.toGeographicLocationConstraint
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-
-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.trySendBlocking(RequestFetch.ForRealLocation)
- }
- is TunnelState.Connecting -> location = newState.location
- is TunnelState.Connected -> {
- location = newState.location
- fetchRequestChannel.trySendBlocking(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
- }
- }
-
- init {
- endpoint.connectionProxy.onStateChange.subscribe(this) { newState -> state = newState }
-
- endpoint.connectivityListener.connectivityNotifier.subscribe(this) { isConnected ->
- if (isConnected && state is TunnelState.Disconnected) {
- fetchRequestChannel.trySendBlocking(RequestFetch.ForRealLocation)
- }
- }
-
- endpoint.settingsListener.relaySettingsNotifier.subscribe(this, ::updateSelectedLocation)
- }
-
- fun onDestroy() {
- endpoint.connectionProxy.onStateChange.unsubscribe(this)
- endpoint.connectivityListener.connectivityNotifier.unsubscribe(this)
- endpoint.settingsListener.relaySettingsNotifier.unsubscribe(this)
-
- 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?.toGeographicLocationConstraint()?.location
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayListListener.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayListListener.kt
deleted file mode 100644
index 1abf64907c..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayListListener.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-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.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.LocationConstraint
-import net.mullvad.mullvadvpn.model.RelayConstraintsUpdate
-import net.mullvad.mullvadvpn.model.RelayList
-import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
-import net.mullvad.mullvadvpn.model.WireguardConstraints
-import net.mullvad.mullvadvpn.service.MullvadDaemon
-
-class RelayListListener(endpoint: ServiceEndpoint) {
- companion object {
- private enum class Command {
- SetRelayLocation,
- SetWireguardConstraints
- }
- }
-
- private val commandChannel = spawnActor()
- private val daemon = endpoint.intermittentDaemon
-
- private var selectedRelayLocation by
- observable<GeographicLocationConstraint?>(null) { _, _, _ ->
- commandChannel.trySendBlocking(Command.SetRelayLocation)
- }
- private var selectedWireguardConstraints by
- observable<WireguardConstraints?>(null) { _, _, _ ->
- commandChannel.trySendBlocking(Command.SetWireguardConstraints)
- }
-
- var relayList by
- observable<RelayList?>(null) { _, _, relays ->
- endpoint.sendEvent(Event.NewRelayList(relays))
- }
- private set
-
- init {
- daemon.registerListener(this) { newDaemon ->
- newDaemon?.let { daemon ->
- setUpListener(daemon)
- fetchInitialRelayList(daemon)
- }
- }
-
- endpoint.dispatcher.registerHandler(Request.SetRelayLocation::class) { request ->
- selectedRelayLocation = request.relayLocation
- }
-
- endpoint.dispatcher.registerHandler(Request.SetWireguardConstraints::class) { request ->
- selectedWireguardConstraints = request.wireguardConstraints
- }
- }
-
- fun onDestroy() {
- commandChannel.close()
- daemon.unregisterListener(this)
- }
-
- private fun setUpListener(daemon: MullvadDaemon) {
- daemon.onRelayListChange = { relayLocations -> relayList = relayLocations }
- }
-
- private fun fetchInitialRelayList(daemon: MullvadDaemon) {
- synchronized(this) {
- if (relayList == null) {
- relayList = daemon.getRelayLocations()
- }
- }
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.CONFLATED) {
- try {
- for (command in channel) {
- when (command) {
- Command.SetRelayLocation,
- Command.SetWireguardConstraints -> updateRelayConstraints()
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Closed sender, so stop the actor
- }
- }
-
- private suspend fun updateRelayConstraints() {
- val location: Constraint<LocationConstraint> =
- selectedRelayLocation?.let { location ->
- Constraint.Only(LocationConstraint.Location(location))
- }
- ?: Constraint.Any()
- val wireguardConstraints: WireguardConstraints? = selectedWireguardConstraints
-
- val update =
- RelaySettingsUpdate.Normal(RelayConstraintsUpdate(location, wireguardConstraints))
-
- daemon.await().updateRelaySettings(update)
- }
-}
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
deleted file mode 100644
index cafb652014..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import android.content.Context
-import android.os.Looper
-import android.os.Messenger
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-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.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>,
- val connectivityListener: ConnectivityListener,
- context: Context
-) {
- companion object {
- sealed class Command {
- data class RegisterListener(val listener: Messenger) : Command()
- data class UnregisterListener(val listenerId: Int) : Command()
- }
- }
-
- private val listeners = mutableMapOf<Int, Messenger>()
- private val commands: SendChannel<Command> = startRegistrator()
-
- internal val dispatcher = DispatchingHandler(looper) { message -> Request.fromMessage(message) }
-
- private var listenerIdCounter = 0
-
- val messenger = Messenger(dispatcher)
-
- val vpnPermission = VpnPermission(context, this)
-
- val connectionProxy = ConnectionProxy(vpnPermission, this)
- val settingsListener = SettingsListener(this)
-
- val accountCache = AccountCache(this)
- val appVersionInfoCache = AppVersionInfoCache(this)
- val authTokenCache = AuthTokenCache(this)
- val customDns = CustomDns(this)
- val locationInfoCache = LocationInfoCache(this)
- val relayListListener = RelayListListener(this)
- val splitTunneling = SplitTunneling(SplitTunnelingPersistence(context), this)
- val voucherRedeemer = VoucherRedeemer(this)
-
- private val deviceRepositoryBackend = DaemonDeviceDataSource(this)
-
- init {
- dispatcher.apply {
- registerHandler(Request.RegisterListener::class) { request ->
- commands.trySendBlocking(Command.RegisterListener(request.listener))
- }
-
- registerHandler(Request.UnregisterListener::class) { request ->
- commands.trySendBlocking(Command.UnregisterListener(request.listenerId))
- }
- }
- }
-
- fun onDestroy() {
- dispatcher.onDestroy()
- commands.close()
-
- accountCache.onDestroy()
- appVersionInfoCache.onDestroy()
- authTokenCache.onDestroy()
- connectionProxy.onDestroy()
- customDns.onDestroy()
- deviceRepositoryBackend.onDestroy()
- locationInfoCache.onDestroy()
- relayListListener.onDestroy()
- settingsListener.onDestroy()
- splitTunneling.onDestroy()
- voucherRedeemer.onDestroy()
- }
-
- internal fun sendEvent(event: Event) {
- synchronized(this) {
- val deadListeners = mutableSetOf<Int>()
-
- for ((id, listener) in listeners) {
- if (!listener.trySendEvent(event, SHOULD_LOG_DEAD_OBJECT_EXCEPTION)) {
- deadListeners.add(id)
- }
- }
- deadListeners.forEach { listeners.remove(it) }
- }
- }
-
- private fun startRegistrator() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- for (command in channel) {
- when (command) {
- is Command.RegisterListener -> {
- intermittentDaemon.await()
-
- registerListener(command.listener)
- }
- is Command.UnregisterListener -> unregisterListener(command.listenerId)
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Registration queue closed; stop registrator
- }
- }
-
- private fun registerListener(listener: Messenger) {
- synchronized(this) {
- val listenerId = newListenerId()
-
- listeners.put(listenerId, listener)
-
- val initialEvents =
- mutableListOf(
- Event.TunnelStateChange(connectionProxy.state),
- Event.AccountHistoryEvent(accountCache.onAccountHistoryChange.latestEvent),
- Event.SettingsUpdate(settingsListener.settings),
- Event.NewLocation(locationInfoCache.location),
- Event.SplitTunnelingUpdate(splitTunneling.onChange.latestEvent),
- Event.CurrentVersion(appVersionInfoCache.currentVersion),
- Event.AppVersionInfo(appVersionInfoCache.appVersionInfo),
- Event.NewRelayList(relayListListener.relayList),
- Event.AuthToken(authTokenCache.authToken),
- Event.ListenerReady(messenger, listenerId)
- )
-
- if (vpnPermission.waitingForResponse) {
- initialEvents.add(Event.VpnPermissionRequest)
- }
-
- val didSuccessfullySendAllMessages =
- initialEvents.all { event ->
- listener.trySendEvent(event, SHOULD_LOG_DEAD_OBJECT_EXCEPTION)
- }
- if (didSuccessfullySendAllMessages.not()) {
- listeners.remove(listenerId)
- }
- }
- }
-
- private fun unregisterListener(listenerId: Int) {
- synchronized(this) { listeners.remove(listenerId) }
- }
-
- private fun newListenerId(): Int {
- val listenerId = listenerIdCounter
-
- listenerIdCounter += 1
-
- return listenerId
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SettingsListener.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SettingsListener.kt
deleted file mode 100644
index 2863594cb9..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SettingsListener.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.model.*
-import net.mullvad.mullvadvpn.service.MullvadDaemon
-import net.mullvad.talpid.util.EventNotifier
-
-class SettingsListener(endpoint: ServiceEndpoint) {
- private sealed class Command {
- class SetAllowLan(val allow: Boolean) : Command()
- class SetAutoConnect(val autoConnect: Boolean) : Command()
- class SetWireGuardMtu(val mtu: Int?) : Command()
- class SetObfuscationSettings(val settings: ObfuscationSettings?) : Command()
- class SetQuantumResistant(val quantumResistant: QuantumResistantState) : Command()
- }
-
- private val commandChannel = spawnActor()
- private val daemon = endpoint.intermittentDaemon
-
- val dnsOptionsNotifier = EventNotifier<DnsOptions?>(null)
- val relaySettingsNotifier = EventNotifier<RelaySettings?>(null)
- val obfuscationSettingsNotifier = EventNotifier<ObfuscationSettings?>(null)
- val settingsNotifier = EventNotifier<Settings?>(null)
-
- var settings by settingsNotifier.notifiable()
- private set
-
- init {
- daemon.registerListener(this) { newDaemon ->
- if (newDaemon != null) {
- registerListener(newDaemon)
- fetchInitialSettings(newDaemon)
- }
- }
-
- settingsNotifier.subscribe(this) { settings ->
- endpoint.sendEvent(Event.SettingsUpdate(settings))
- }
-
- endpoint.dispatcher.apply {
- registerHandler(Request.SetAllowLan::class) { request ->
- commandChannel.trySendBlocking(Command.SetAllowLan(request.allow))
- }
-
- registerHandler(Request.SetAutoConnect::class) { request ->
- commandChannel.trySendBlocking(Command.SetAutoConnect(request.autoConnect))
- }
-
- registerHandler(Request.SetWireGuardMtu::class) { request ->
- commandChannel.trySendBlocking(Command.SetWireGuardMtu(request.mtu))
- }
-
- registerHandler(Request.SetObfuscationSettings::class) { request ->
- commandChannel.trySendBlocking(Command.SetObfuscationSettings(request.settings))
- }
-
- registerHandler(Request.SetWireGuardQuantumResistant::class) { request ->
- commandChannel.trySendBlocking(
- Command.SetQuantumResistant(request.quantumResistant)
- )
- }
- }
- }
-
- fun onDestroy() {
- commandChannel.close()
- daemon.unregisterListener(this)
-
- dnsOptionsNotifier.unsubscribeAll()
- relaySettingsNotifier.unsubscribeAll()
- obfuscationSettingsNotifier.unsubscribeAll()
- settingsNotifier.unsubscribeAll()
- }
-
- fun subscribe(id: Any, listener: (Settings) -> Unit) {
- settingsNotifier.subscribe(id) { maybeSettings ->
- maybeSettings?.let { settings -> listener(settings) }
- }
- }
-
- fun unsubscribe(id: Any) {
- settingsNotifier.unsubscribe(id)
- }
-
- private fun registerListener(daemon: MullvadDaemon) {
- daemon.onSettingsChange.subscribe(this, ::handleNewSettings)
- }
-
- private fun fetchInitialSettings(daemon: MullvadDaemon) {
- synchronized(this) { handleNewSettings(daemon.getSettings()) }
- }
-
- private fun handleNewSettings(newSettings: Settings?) {
- if (newSettings != null) {
- synchronized(this) {
- if (settings?.tunnelOptions?.dnsOptions != newSettings.tunnelOptions.dnsOptions) {
- dnsOptionsNotifier.notify(newSettings.tunnelOptions.dnsOptions)
- }
-
- if (settings?.relaySettings != newSettings.relaySettings) {
- relaySettingsNotifier.notify(newSettings.relaySettings)
- }
-
- if (settings?.obfuscationSettings != newSettings.obfuscationSettings) {
- obfuscationSettingsNotifier.notify(newSettings.obfuscationSettings)
- }
-
- settings = newSettings
- }
- }
- }
-
- private fun spawnActor() =
- GlobalScope.actor<Command>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- for (command in channel) {
- when (command) {
- is Command.SetAllowLan -> daemon.await().setAllowLan(command.allow)
- is Command.SetAutoConnect ->
- daemon.await().setAutoConnect(command.autoConnect)
- is Command.SetWireGuardMtu -> daemon.await().setWireguardMtu(command.mtu)
- is Command.SetObfuscationSettings ->
- daemon.await().setObfuscationSettings(command.settings)
- is Command.SetQuantumResistant ->
- daemon.await().setQuantumResistant(command.quantumResistant)
- }
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Closed sender, so stop the actor
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SplitTunneling.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SplitTunneling.kt
deleted file mode 100644
index a683b1e4bf..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/SplitTunneling.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlin.properties.Delegates.observable
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.service.persistence.SplitTunnelingPersistence
-import net.mullvad.talpid.util.EventNotifier
-
-class SplitTunneling(persistence: SplitTunnelingPersistence, endpoint: ServiceEndpoint) {
- private val excludedApps = persistence.excludedApps.toMutableSet()
-
- private var enabled by
- observable(persistence.enabled) { _, wasEnabled, isEnabled ->
- if (wasEnabled != isEnabled) {
- persistence.enabled = isEnabled
- update()
- }
- }
-
- val onChange = EventNotifier<List<String>?>(excludedApps.toList())
-
- init {
- onChange.subscribe(this) { excludedApps ->
- endpoint.sendEvent(Event.SplitTunnelingUpdate(excludedApps))
- }
-
- endpoint.dispatcher.apply {
- registerHandler(Request.IncludeApp::class) { request ->
- excludedApps.remove(request.packageName)
- update()
- }
-
- registerHandler(Request.ExcludeApp::class) { request ->
- excludedApps.add(request.packageName)
- update()
- }
-
- registerHandler(Request.SetEnableSplitTunneling::class) { request ->
- enabled = request.enable
- }
-
- registerHandler(Request.PersistExcludedApps::class) { _ ->
- persistence.excludedApps = excludedApps
- }
- }
- }
-
- fun onDestroy() {
- onChange.unsubscribeAll()
- }
-
- private fun update() {
- if (enabled) {
- onChange.notify(excludedApps.toList())
- } else {
- onChange.notify(null)
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VoucherRedeemer.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VoucherRedeemer.kt
deleted file mode 100644
index a7003d6888..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VoucherRedeemer.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-
-class VoucherRedeemer(private val endpoint: ServiceEndpoint) {
- private val daemon
- get() = endpoint.intermittentDaemon
-
- private val voucherChannel = spawnActor()
-
- init {
- endpoint.dispatcher.registerHandler(Request.SubmitVoucher::class) { request ->
- voucherChannel.trySendBlocking(request.voucher)
- }
- }
-
- fun onDestroy() {
- voucherChannel.close()
- }
-
- private fun spawnActor() =
- GlobalScope.actor<String>(Dispatchers.Default, Channel.UNLIMITED) {
- try {
- for (voucher in channel) {
- val result = daemon.await().submitVoucher(voucher)
-
- endpoint.sendEvent(Event.VoucherSubmissionResult(voucher, result))
- }
- } catch (exception: ClosedReceiveChannelException) {
- // Voucher channel was closed, stop the actor
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VpnPermission.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VpnPermission.kt
deleted file mode 100644
index c86c471a3d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/VpnPermission.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import android.content.Context
-import android.content.Intent
-import android.net.VpnService
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.util.Intermittent
-
-class VpnPermission(private val context: Context, private val endpoint: ServiceEndpoint) {
- private val isGranted = Intermittent<Boolean>()
-
- var waitingForResponse = false
- private set
-
- init {
- endpoint.dispatcher.registerHandler(Request.VpnPermissionResponse::class) { request ->
- waitingForResponse = false
- isGranted.spawnUpdate(request.isGranted)
- }
- }
-
- suspend fun request(): Boolean {
- val intent = VpnService.prepare(context)
-
- if (intent == null) {
- isGranted.update(true)
- } else {
- val activityIntent =
- Intent(context, MainActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- }
-
- isGranted.update(null)
- waitingForResponse = true
-
- context.startActivity(activityIntent)
- endpoint.sendEvent(Event.VpnPermissionRequest)
- }
-
- return isGranted.await()
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt
deleted file mode 100644
index adaebd5119..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-package net.mullvad.mullvadvpn.service.notifications
-
-import android.app.Notification
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import androidx.core.app.NotificationCompat
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.delay
-import net.mullvad.mullvadvpn.BuildConfig
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
-import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
-import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.isNotificationPermissionGranted
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.service.MullvadDaemon
-import net.mullvad.mullvadvpn.service.endpoint.AccountCache
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.util.Intermittent
-import net.mullvad.mullvadvpn.util.JobTracker
-import org.joda.time.DateTime
-import org.joda.time.Duration
-
-class AccountExpiryNotification(
- val context: Context,
- val daemon: Intermittent<MullvadDaemon>,
- val accountCache: AccountCache
-) {
- companion object {
- val NOTIFICATION_ID: Int = 2
- val REMAINING_TIME_FOR_REMINDERS = Duration.standardDays(2)
- val TIME_BETWEEN_CHECKS: Long = 12 /* h */ * 60 /* min */ * 60 /* s */ * 1000 /* ms */
- }
-
- private val jobTracker = JobTracker()
- private val resources = context.resources
-
- private val buyMoreTimeUrl = resources.getString(R.string.account_url)
-
- private val channel =
- NotificationChannel(
- context,
- "mullvad_account_time",
- NotificationCompat.VISIBILITY_PRIVATE,
- R.string.account_time_notification_channel_name,
- R.string.account_time_notification_channel_description,
- NotificationManager.IMPORTANCE_HIGH,
- true,
- true
- )
-
- var accountExpiry by
- observable<AccountExpiry>(AccountExpiry.Missing) { _, oldValue, newValue ->
- if (oldValue != newValue) {
- jobTracker.newUiJob("update") { update(newValue) }
- }
- }
-
- init {
- accountCache.onAccountExpiryChange.subscribe(this) { expiry -> accountExpiry = expiry }
- }
-
- fun onDestroy() {
- accountCache.onAccountExpiryChange.unsubscribe(this)
- }
-
- private suspend fun update(expiry: AccountExpiry) {
- val expiryDate = expiry.date()
- val durationUntilExpiry = expiryDate?.remainingTime()
-
- if (accountCache.isNewAccount.not() && durationUntilExpiry?.isCloseToExpiry() == true) {
- if (context.isNotificationPermissionGranted()) {
- val notification = build(expiryDate, durationUntilExpiry)
- channel.notificationManager.notify(NOTIFICATION_ID, notification)
- }
- jobTracker.newUiJob("scheduleUpdate") { scheduleUpdate() }
- } else {
- channel.notificationManager.cancel(NOTIFICATION_ID)
- jobTracker.cancelJob("scheduleUpdate")
- }
- }
-
- private fun DateTime.remainingTime(): Duration {
- return Duration(DateTime.now(), this)
- }
-
- private fun Duration.isCloseToExpiry(): Boolean {
- return isShorterThan(REMAINING_TIME_FOR_REMINDERS)
- }
-
- private suspend fun scheduleUpdate() {
- delay(TIME_BETWEEN_CHECKS)
- update(accountExpiry)
- }
-
- private suspend fun build(expiry: DateTime, remainingTime: Duration): Notification {
- val url =
- jobTracker.runOnBackground {
- Uri.parse("$buyMoreTimeUrl?token=${daemon.await().getWwwAuthToken()}")
- }
- val intent =
- if (BuildTypes.RELEASE == BuildConfig.BUILD_TYPE) {
- Intent(context, MainActivity::class.java)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
- .setAction(Intent.ACTION_MAIN)
- } else {
- Intent(Intent.ACTION_VIEW, url)
- }
- val pendingIntent =
- PendingIntent.getActivity(context, 1, intent, SdkUtils.getSupportedPendingIntentFlags())
-
- return channel.buildNotification(pendingIntent, format(expiry, remainingTime))
- }
-
- private fun format(expiry: DateTime, remainingTime: Duration): String {
- if (remainingTime.isShorterThan(Duration.ZERO)) {
- return resources.getString(R.string.account_credit_has_expired)
- } else {
- val remainingTimeInfo = remainingTime.toPeriodTo(expiry)
-
- if (remainingTimeInfo.days >= 1) {
- return getRemainingText(
- R.plurals.account_credit_expires_in_days,
- remainingTime.standardDays.toInt()
- )
- } else if (remainingTimeInfo.hours >= 1) {
- return getRemainingText(
- R.plurals.account_credit_expires_in_hours,
- remainingTime.standardHours.toInt()
- )
- } else {
- return resources.getString(R.string.account_credit_expires_in_a_few_minutes)
- }
- }
- }
-
- private fun getRemainingText(pluralId: Int, quantity: Int): String {
- return resources.getQuantityString(pluralId, quantity, quantity)
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt
deleted file mode 100644
index de557aaf22..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package net.mullvad.mullvadvpn.service.notifications
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.content.Context
-import androidx.core.app.NotificationChannelCompat
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import net.mullvad.mullvadvpn.R
-
-class NotificationChannel(
- val context: Context,
- val id: String,
- val visibility: Int,
- name: Int,
- description: Int,
- importance: Int,
- isVibrationEnabled: Boolean,
- isBadgeEnabled: Boolean
-) {
- private val badgeColor by lazy { context.getColor(R.color.colorPrimary) }
-
- val notificationManager = NotificationManagerCompat.from(context)
-
- init {
- val channelName = context.getString(name)
- val channelDescription = context.getString(description)
-
- val channel =
- NotificationChannelCompat.Builder(id, importance)
- .setName(channelName)
- .setDescription(channelDescription)
- .setShowBadge(isBadgeEnabled)
- .setVibrationEnabled(isVibrationEnabled)
- .build()
-
- notificationManager.createNotificationChannel(channel)
- }
-
- fun buildNotification(
- intent: PendingIntent,
- title: String,
- deleteIntent: PendingIntent? = null,
- isOngoing: Boolean = false
- ): Notification {
- return buildNotification(intent, title, emptyList(), deleteIntent, isOngoing)
- }
-
- fun buildNotification(
- intent: PendingIntent,
- title: Int,
- deleteIntent: PendingIntent? = null,
- isOngoing: Boolean = false
- ): Notification {
- return buildNotification(intent, title, emptyList(), deleteIntent, isOngoing)
- }
-
- fun buildNotification(
- pendingIntent: PendingIntent,
- title: Int,
- actions: List<NotificationCompat.Action>,
- deleteIntent: PendingIntent? = null,
- isOngoing: Boolean = false
- ): Notification {
- return buildNotification(
- pendingIntent,
- context.getString(title),
- actions,
- deleteIntent,
- isOngoing
- )
- }
-
- private fun buildNotification(
- pendingIntent: PendingIntent,
- title: String,
- actions: List<NotificationCompat.Action>,
- deleteIntent: PendingIntent? = null,
- isOngoing: Boolean = false
- ): Notification {
- val builder =
- NotificationCompat.Builder(context, id)
- .setSmallIcon(R.drawable.small_logo_black)
- .setColor(badgeColor)
- .setContentTitle(title)
- .setContentIntent(pendingIntent)
- .setVisibility(visibility)
- .setOngoing(isOngoing)
- for (action in actions) {
- builder.addAction(action)
- }
-
- deleteIntent?.let { intent -> builder.setDeleteIntent(intent) }
-
- return builder.build()
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
deleted file mode 100644
index 81ed83ea44..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-package net.mullvad.mullvadvpn.service.notifications
-
-import android.app.Notification
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import androidx.core.app.NotificationCompat
-import kotlin.properties.Delegates.observable
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.util.getErrorNotificationResources
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-import net.mullvad.talpid.tunnel.ErrorStateCause
-
-class TunnelStateNotification(val context: Context) {
- companion object {
- val NOTIFICATION_ID: Int = 1
- }
-
- private val channel =
- NotificationChannel(
- context,
- "vpn_tunnel_status",
- NotificationCompat.VISIBILITY_SECRET,
- R.string.foreground_notification_channel_name,
- R.string.foreground_notification_channel_description,
- NotificationManager.IMPORTANCE_MIN,
- false,
- false
- )
-
- private val notificationText: Int
- get() =
- when (val state = tunnelState) {
- is TunnelState.Disconnected -> R.string.unsecured
- is TunnelState.Connecting -> {
- if (reconnecting) {
- R.string.reconnecting
- } else {
- R.string.connecting
- }
- }
- is TunnelState.Connected -> R.string.secured
- is TunnelState.Disconnecting -> {
- when (state.actionAfterDisconnect) {
- ActionAfterDisconnect.Reconnect -> R.string.reconnecting
- else -> R.string.disconnecting
- }
- }
- is TunnelState.Error -> {
- if (state.isDeviceOffline()) {
- R.string.blocking_internet_device_offline
- } else {
- state.errorState.getErrorNotificationResources(context).titleResourceId
- }
- }
- }
-
- private fun TunnelState.isDeviceOffline(): Boolean {
- return (this as? TunnelState.Error)?.errorState?.cause is ErrorStateCause.IsOffline
- }
-
- private val shouldDisplayOngoingNotification: Boolean
- get() =
- when (tunnelState) {
- is TunnelState.Connected -> true
- is TunnelState.Disconnected,
- is TunnelState.Connecting,
- is TunnelState.Disconnecting,
- is TunnelState.Error -> false
- }
-
- private var reconnecting = false
- private var showingReconnecting = false
-
- var showAction by observable(false) { _, _, _ -> update() }
-
- var tunnelState by
- observable<TunnelState>(TunnelState.Disconnected) { _, _, newState ->
- val isReconnecting = newState is TunnelState.Connecting && reconnecting
- val shouldBeginReconnecting =
- (newState as? TunnelState.Disconnecting)?.actionAfterDisconnect ==
- ActionAfterDisconnect.Reconnect
- reconnecting = isReconnecting || shouldBeginReconnecting
- update()
- }
-
- var visible by
- observable(true) { _, _, newValue ->
- if (newValue == true) {
- update()
- } else {
- channel.notificationManager.cancel(NOTIFICATION_ID)
- }
- }
-
- private fun update() {
- if (visible && (!reconnecting || !showingReconnecting)) {
- channel.notificationManager.notify(NOTIFICATION_ID, build())
- }
- }
-
- fun build(): Notification {
- val intent =
- Intent(context, MainActivity::class.java)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
- .setAction(Intent.ACTION_MAIN)
- val pendingIntent =
- PendingIntent.getActivity(context, 1, intent, SdkUtils.getSupportedPendingIntentFlags())
- val actions =
- if (showAction) {
- listOf(buildAction())
- } else {
- emptyList()
- }
-
- return channel.buildNotification(
- pendingIntent,
- notificationText,
- actions,
- isOngoing = shouldDisplayOngoingNotification
- )
- }
-
- private fun buildAction(): NotificationCompat.Action {
- val action = TunnelStateNotificationAction.from(tunnelState)
- val label = context.getString(action.text)
- val intent = Intent(action.key).setPackage("net.mullvad.mullvadvpn")
- val pendingIntent =
- PendingIntent.getForegroundService(
- context,
- 1,
- intent,
- SdkUtils.getSupportedPendingIntentFlags()
- )
-
- return NotificationCompat.Action(action.icon, label, pendingIntent)
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt
deleted file mode 100644
index 9ed9998054..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.mullvad.mullvadvpn.service.notifications
-
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.service.MullvadVpnService
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-
-enum class TunnelStateNotificationAction {
- Connect,
- Disconnect,
- Cancel,
- Dismiss;
-
- companion object {
- fun from(tunnelState: TunnelState) =
- when (tunnelState) {
- is TunnelState.Disconnected -> Connect
- is TunnelState.Connecting -> Cancel
- is TunnelState.Connected -> Disconnect
- is TunnelState.Disconnecting -> {
- when (tunnelState.actionAfterDisconnect) {
- ActionAfterDisconnect.Reconnect -> Cancel
- else -> Connect
- }
- }
- is TunnelState.Error -> {
- if (tunnelState.errorState.isBlocking) {
- Disconnect
- } else {
- Dismiss
- }
- }
- }
- }
-
- val text
- get() =
- when (this) {
- Connect -> R.string.connect
- Disconnect -> R.string.disconnect
- Cancel -> R.string.cancel
- Dismiss -> R.string.dismiss
- }
-
- val key
- get() =
- when (this) {
- Connect -> MullvadVpnService.KEY_CONNECT_ACTION
- else -> MullvadVpnService.KEY_DISCONNECT_ACTION
- }
-
- val icon
- get() =
- when (this) {
- Connect -> R.drawable.icon_notification_connect
- else -> R.drawable.icon_notification_disconnect
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/persistence/SplitTunnelingPersistence.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/persistence/SplitTunnelingPersistence.kt
deleted file mode 100644
index 264304ab3f..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/persistence/SplitTunnelingPersistence.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package net.mullvad.mullvadvpn.service.persistence
-
-import android.content.Context
-import java.io.File
-import kotlin.properties.Delegates.observable
-
-// The spelling of the shared preferences location can't be changed to American English without
-// either having users lose their preferences on update or implementing some migration code.
-private const val SHARED_PREFERENCES = "split_tunnelling"
-private const val KEY_ENABLED = "enabled"
-
-class SplitTunnelingPersistence(context: Context) {
- // The spelling of the app list file name can't be changed to American English without either
- // having users lose their preferences on update or implementing some migration code.
- private val appListFile = File(context.filesDir, "split-tunnelling.txt")
- private val preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE)
-
- var enabled by
- observable(preferences.getBoolean(KEY_ENABLED, false)) { _, _, isEnabled ->
- preferences.edit().apply {
- putBoolean(KEY_ENABLED, isEnabled)
- apply()
- }
- }
-
- var excludedApps by
- observable(loadExcludedApps()) { _, _, excludedAppsSet ->
- appListFile.writeText(excludedAppsSet.joinToString(separator = "\n"))
- }
-
- private fun loadExcludedApps(): Set<String> {
- return when {
- appListFile.exists() -> appListFile.readLines().toSet()
- else -> emptySet()
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
index d67c6e1dfb..11dcaf5067 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/extension/ContextExtensions.kt
@@ -1,34 +1,8 @@
package net.mullvad.mullvadvpn.ui.extension
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.provider.Settings
import androidx.fragment.app.Fragment
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
import net.mullvad.mullvadvpn.ui.MainActivity
-private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
-
-fun Context.openAccountPageInBrowser(authToken: String) {
- startActivity(
- Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.account_url) + "?token=$authToken"))
- )
-}
-
-fun Context.getAlwaysOnVpnAppName(): String? {
- return resolveAlwaysOnVpnPackageName()
- ?.let { currentAlwaysOnVpn ->
- packageManager.getInstalledPackagesList(0).singleOrNull {
- it.packageName == currentAlwaysOnVpn && it.packageName != packageName
- }
- }
- ?.applicationInfo
- ?.loadLabel(packageManager)
- ?.toString()
-}
-
fun Fragment.requireMainActivity(): MainActivity {
return if (this.activity is MainActivity) {
this.activity as MainActivity
@@ -38,18 +12,3 @@ fun Fragment.requireMainActivity(): MainActivity {
)
}
}
-
-// NOTE: This function will return the current Always-on VPN package's name. In case of either
-// Always-on VPN being disabled or not being able to read the state, NULL will be returned.
-fun Context.resolveAlwaysOnVpnPackageName(): String? {
- return try {
- Settings.Secure.getString(contentResolver, ALWAYS_ON_VPN_APP)
- } catch (ex: SecurityException) {
- null
- }
-}
-
-fun Context.openLink(uri: Uri) {
- val intent = Intent(Intent.ACTION_VIEW, uri)
- startActivity(intent)
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AccountFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AccountFragment.kt
index 06fdb37b79..bf6dc71b22 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AccountFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/AccountFragment.kt
@@ -20,7 +20,10 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
+import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
+import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
@@ -29,7 +32,6 @@ import net.mullvad.mullvadvpn.ui.GroupedPasswordTransformationMethod
import net.mullvad.mullvadvpn.ui.GroupedTransformationMethod
import net.mullvad.mullvadvpn.ui.NavigationBarPainter
import net.mullvad.mullvadvpn.ui.StatusBarPainter
-import net.mullvad.mullvadvpn.ui.extension.openAccountPageInBrowser
import net.mullvad.mullvadvpn.ui.extension.requireMainActivity
import net.mullvad.mullvadvpn.ui.paintNavigationBar
import net.mullvad.mullvadvpn.ui.paintStatusBar
@@ -41,11 +43,9 @@ import net.mullvad.mullvadvpn.ui.widget.Button
import net.mullvad.mullvadvpn.ui.widget.CopyableInformationView
import net.mullvad.mullvadvpn.ui.widget.InformationView
import net.mullvad.mullvadvpn.ui.widget.RedeemVoucherButton
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.util.UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS
import net.mullvad.mullvadvpn.util.addDebounceForUnknownState
import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier
-import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
import net.mullvad.talpid.tunnel.ErrorStateCause
import org.joda.time.DateTime
import org.koin.android.ext.android.inject
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt
index 725c70eeb6..a8bdc2d53a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt
@@ -18,7 +18,8 @@ import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.screen.ConnectScreen
import net.mullvad.mullvadvpn.compose.theme.AppTheme
-import net.mullvad.mullvadvpn.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.ui.NavigationBarPainter
@@ -30,7 +31,6 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.ui.widget.NotificationBanner
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import net.mullvad.talpid.tunnel.ErrorStateCause
import org.koin.android.ext.android.inject
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/LoginFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/LoginFragment.kt
index ba29326b3d..c02ed0b652 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/LoginFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/LoginFragment.kt
@@ -16,6 +16,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.ui.LoginState
import net.mullvad.mullvadvpn.ui.NavigationBarPainter
import net.mullvad.mullvadvpn.ui.extension.requireMainActivity
@@ -24,7 +25,6 @@ import net.mullvad.mullvadvpn.ui.widget.AccountInput
import net.mullvad.mullvadvpn.ui.widget.AccountLogin
import net.mullvad.mullvadvpn.ui.widget.Button
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/OutOfTimeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/OutOfTimeFragment.kt
index 18ac784f4b..954e9dcedf 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/OutOfTimeFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/OutOfTimeFragment.kt
@@ -18,10 +18,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
+import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.ui.extension.openAccountPageInBrowser
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
@@ -30,7 +31,6 @@ import net.mullvad.mullvadvpn.ui.widget.Button
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.ui.widget.RedeemVoucherButton
import net.mullvad.mullvadvpn.ui.widget.SitePaymentButton
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.tunnel.ErrorStateCause
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
index 6bc0192c73..10d2e5e249 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
@@ -11,11 +11,11 @@ import androidx.fragment.app.Fragment
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.screen.PrivacyDisclaimerScreen
import net.mullvad.mullvadvpn.compose.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild
import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras
import net.mullvad.mullvadvpn.ui.MainActivity
import net.mullvad.mullvadvpn.ui.NavigationBarPainter
import net.mullvad.mullvadvpn.ui.StatusBarPainter
-import net.mullvad.mullvadvpn.util.appendHideNavOnReleaseBuild
import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel
import org.koin.android.ext.android.inject
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
index d38b905a21..4d5ca3c9b5 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ProblemReportFragment.kt
@@ -21,9 +21,9 @@ import kotlin.properties.Delegates.observable
import kotlinx.coroutines.CompletableDeferred
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.ui.CollapsibleTitleController
import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.util.JobTracker
class ProblemReportFragment : BaseFragment() {
private val jobTracker = JobTracker()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
index a2c369bb15..59318a8268 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/RedeemVoucherDialogFragment.kt
@@ -14,6 +14,7 @@ import android.widget.EditText
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.model.VoucherSubmissionError
import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
import net.mullvad.mullvadvpn.repository.AccountRepository
@@ -21,7 +22,6 @@ import net.mullvad.mullvadvpn.ui.MainActivity
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
import net.mullvad.mullvadvpn.ui.widget.Button
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.util.SegmentedInputFormatter
import org.joda.time.DateTime
import org.koin.android.ext.android.inject
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ViewLogsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ViewLogsFragment.kt
index b655c007c6..e519526f52 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ViewLogsFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ViewLogsFragment.kt
@@ -8,8 +8,8 @@ import android.view.ViewGroup
import android.widget.EditText
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.mullvadvpn.util.JobTracker
class ViewLogsFragment : BaseFragment() {
private val jobTracker = JobTracker()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/WelcomeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/WelcomeFragment.kt
index 13f951b04a..a995e4f5b4 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/WelcomeFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/WelcomeFragment.kt
@@ -21,18 +21,18 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
+import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.ui.extension.openAccountPageInBrowser
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.ui.widget.RedeemVoucherButton
import net.mullvad.mullvadvpn.ui.widget.SitePaymentButton
-import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.util.UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS
import net.mullvad.mullvadvpn.util.addDebounceForUnknownState
import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/InAppNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/InAppNotification.kt
index af4d34e9c1..9ee7a02698 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/InAppNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/InAppNotification.kt
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.ui.notification
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.util.ChangeMonitor
-import net.mullvad.mullvadvpn.util.JobTracker
abstract class InAppNotification {
private val changeMonitor = ChangeMonitor()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
index 01d8cfdacf..3c76a4d4eb 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/TunnelStateNotification.kt
@@ -2,8 +2,8 @@ package net.mullvad.mullvadvpn.ui.notification
import android.content.Context
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.common.util.getErrorNotificationResources
import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.util.getErrorNotificationResources
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.tunnel.ErrorState
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/VersionInfoNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/VersionInfoNotification.kt
index c37b5e5220..6e0e5f9846 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/VersionInfoNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/VersionInfoNotification.kt
@@ -3,9 +3,9 @@ package net.mullvad.mullvadvpn.ui.notification
import android.content.Context
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
+import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild
import net.mullvad.mullvadvpn.ui.VersionInfo
-import net.mullvad.mullvadvpn.util.appendHideNavOnReleaseBuild
class VersionInfoNotification(val isEnabled: Boolean, context: Context) :
NotificationWithUrl(
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 f31dfd7ce4..7470fc0712 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
@@ -9,8 +9,8 @@ import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.ipc.Request
+import net.mullvad.mullvadvpn.lib.ipc.extensions.trySendRequest
import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.util.trySendRequest
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.util.EventNotifier
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/CustomDns.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/CustomDns.kt
index c9275429ca..b24cda9213 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/CustomDns.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/CustomDns.kt
@@ -3,9 +3,9 @@ package net.mullvad.mullvadvpn.ui.serviceconnection
import android.os.Messenger
import java.net.InetAddress
import net.mullvad.mullvadvpn.lib.ipc.Request
+import net.mullvad.mullvadvpn.lib.ipc.extensions.trySendRequest
import net.mullvad.mullvadvpn.model.DnsOptions
import net.mullvad.mullvadvpn.model.DnsState
-import net.mullvad.mullvadvpn.util.trySendRequest
import net.mullvad.talpid.util.EventNotifier
class CustomDns(private val connection: Messenger, private val settingsListener: SettingsListener) {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
index ed3ca5a618..574f7a2004 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/RelayListListener.kt
@@ -1,6 +1,7 @@
package net.mullvad.mullvadvpn.ui.serviceconnection
import android.os.Messenger
+import net.mullvad.mullvadvpn.lib.common.util.toGeographicLocationConstraint
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.ipc.Request
@@ -14,7 +15,6 @@ import net.mullvad.mullvadvpn.relaylist.RelayCountry
import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.relaylist.findItemForLocation
import net.mullvad.mullvadvpn.relaylist.toRelayCountries
-import net.mullvad.mullvadvpn.util.toGeographicLocationConstraint
class RelayListListener(
private val connection: Messenger,
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 3c048f31be..a9094ed011 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,7 +6,7 @@ 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
-import net.mullvad.mullvadvpn.util.trySendRequest
+import net.mullvad.mullvadvpn.lib.ipc.extensions.trySendRequest
class ServiceConnectionDeviceDataSource(
private val connection: Messenger,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/Button.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/Button.kt
index 8a014a7ded..c2a5f3510b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/Button.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/Button.kt
@@ -8,7 +8,7 @@ import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.util.JobTracker
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
open class Button : FrameLayout {
enum class ButtonColor {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
index da37d1e36d..48cd07f5c2 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/NotificationBanner.kt
@@ -14,10 +14,10 @@ import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.ui.notification.InAppNotification
import net.mullvad.mullvadvpn.ui.notification.InAppNotificationController
import net.mullvad.mullvadvpn.ui.notification.StatusLevel
-import net.mullvad.mullvadvpn.util.JobTracker
class NotificationBanner : FrameLayout {
private val jobTracker = JobTracker()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/RedeemVoucherButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/RedeemVoucherButton.kt
index 885fceef2a..b6d5ddb88d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/RedeemVoucherButton.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/RedeemVoucherButton.kt
@@ -3,8 +3,8 @@ package net.mullvad.mullvadvpn.ui.widget
import android.content.Context
import android.util.AttributeSet
import androidx.fragment.app.FragmentManager
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.ui.fragment.RedeemVoucherDialogFragment
-import net.mullvad.mullvadvpn.util.JobTracker
class RedeemVoucherButton : Button {
constructor(context: Context) : super(context)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Debouncer.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Debouncer.kt
index 677d981417..4d0406cdc3 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Debouncer.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Debouncer.kt
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.util
import kotlin.properties.Delegates.observable
import kotlinx.coroutines.delay
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
// Helper to filter out bursts of events so that only the latest event in an interval is notified.
//
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt
deleted file mode 100644
index 96b991fcf3..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorNotificationMessage.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import android.content.res.Resources
-
-data class ErrorNotificationMessage(
- val titleResourceId: Int,
- val messageResourceId: Int,
- val optionalMessageArgument: String? = null
-) {
- fun getTitleText(resources: Resources): String {
- return resources.getString(titleResourceId)
- }
-
- fun getMessageText(resources: Resources): String {
- return if (optionalMessageArgument != null) {
- resources.getString(messageResourceId, optionalMessageArgument)
- } else {
- resources.getString(messageResourceId)
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt
deleted file mode 100644
index e98799d1d2..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateCauseExtension.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import net.mullvad.mullvadvpn.R
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.tunnel.ParameterGenerationError
-
-fun ErrorStateCause.errorMessageId(): Int {
- return when (this) {
- is ErrorStateCause.InvalidDnsServers -> R.string.invalid_dns_servers
- is ErrorStateCause.AuthFailed -> R.string.auth_failed
- is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
- is ErrorStateCause.SetFirewallPolicyError -> R.string.set_firewall_policy_error
- is ErrorStateCause.SetDnsError -> R.string.set_dns_error
- is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
- is ErrorStateCause.IsOffline -> R.string.is_offline
- is ErrorStateCause.TunnelParameterError -> {
- when (error) {
- ParameterGenerationError.NoMatchingRelay,
- ParameterGenerationError.NoMatchingBridgeRelay -> {
- R.string.no_matching_relay
- }
- ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key
- ParameterGenerationError.CustomTunnelHostResultionError -> {
- R.string.custom_tunnel_host_resolution_error
- }
- }
- }
- is ErrorStateCause.VpnPermissionDenied -> R.string.vpn_permission_denied_error
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt
deleted file mode 100644
index 54d676a9e7..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/ErrorStateExtension.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import android.content.Context
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.ui.extension.getAlwaysOnVpnAppName
-import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.util.addressString
-
-fun ErrorState.getErrorNotificationResources(context: Context): ErrorNotificationMessage {
- return when {
- cause is ErrorStateCause.InvalidDnsServers -> {
- ErrorNotificationMessage(
- R.string.blocking_internet,
- cause.errorMessageId(),
- (cause as ErrorStateCause.InvalidDnsServers).addresses.joinToString { address ->
- address.addressString()
- }
- )
- }
- cause is ErrorStateCause.VpnPermissionDenied -> {
- resolveAlwaysOnVpnErrorNotificationMessage(context.getAlwaysOnVpnAppName())
- }
- isBlocking -> ErrorNotificationMessage(R.string.blocking_internet, cause.errorMessageId())
- else -> ErrorNotificationMessage(R.string.critical_error, R.string.failed_to_block_internet)
- }
-}
-
-private fun resolveAlwaysOnVpnErrorNotificationMessage(
- alwaysOnVpnAppName: String?
-): ErrorNotificationMessage {
- return if (alwaysOnVpnAppName != null) {
- ErrorNotificationMessage(
- R.string.always_on_vpn_error_notification_title,
- R.string.always_on_vpn_error_notification_content,
- alwaysOnVpnAppName
- )
- } else {
- ErrorNotificationMessage(
- R.string.vpn_permission_error_notification_title,
- R.string.vpn_permission_error_notification_message
- )
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Intermittent.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Intermittent.kt
deleted file mode 100644
index dc8ee67cf8..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Intermittent.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.sync.withPermit
-import net.mullvad.talpid.util.EventNotifier
-
-// Wrapper to allow awaiting for intermittent values.
-//
-// Wraps a property that is changed from time to time and that can become unavailable (null). This
-// behaves in a way similar to `CompletableDeferred`, but the value can be set and reset multiple
-// times.
-//
-// Calling `await` will either provide the value if it's available, or suspend until it becomes
-// available and then return it.
-//
-// Calling `update` will set the internal value after it guarantees that no other coroutine is
-// currently reading the value (through a permit from the semaphore). After the value is set, it
-// provides a permit to the semaphore so that suspended coroutines can use the new value.
-//
-// Extra initialization can be done on the intermittent value when it becomes available and before
-// it is provided to the awaiting coroutines, through the use of listener callbacks. These are
-// called after the value is updated but before it is made available to the coroutines.
-class Intermittent<T> {
- private val notifier = EventNotifier<T?>(null)
- private val semaphore = Semaphore(1, 1)
- private val writeLock = Mutex()
-
- private var updateJob: Job? = null
- private var value by notifier.notifiable()
-
- // When the internal value is updated, listeners can be notified before the awaiting coroutines
- // resume execution. This allows performing any extra initialization before the value is made
- // available for usage.
- fun registerListener(id: Any, listener: (T?) -> Unit) = notifier.subscribe(id, listener)
- fun unregisterListener(id: Any) = notifier.unsubscribe(id)
-
- suspend fun await(): T {
- return semaphore.withPermit { value!! }
- }
-
- suspend fun update(newValue: T?) {
- writeLock.withLock {
- if (newValue != value) {
- if (value != null) {
- semaphore.acquire()
- }
-
- // This will trigger the listeners to run before the awaiting coroutines resume
- value = newValue
-
- if (newValue != null) {
- semaphore.release()
- }
- }
- }
- }
-
- // Helper method that spawns a coroutine to update the value.
- fun spawnUpdate(newValue: T?) {
- synchronized(this@Intermittent) {
- val previousUpdate = updateJob
-
- updateJob =
- GlobalScope.launch(Dispatchers.Default) {
- previousUpdate?.join()
- update(newValue)
- }
- }
- }
-
- // Helper method that provides a simple way to change the wrapped value.
- // The method returns a property delegate that will spawn a coroutine to update the wrapped
- // value every time the property is written to.
- fun source() = observable<T?>(null) { _, _, newValue -> spawnUpdate(newValue) }
-
- fun onDestroy() {
- notifier.unsubscribeAll()
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt
deleted file mode 100644
index fa027af4b1..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
-
-class JobTracker {
- private val jobs = HashMap<Long, Job>()
- private val reaperJobs = HashMap<Long, Job>()
- private val namedJobs = HashMap<String, Long>()
-
- private var jobIdCounter = 0L
-
- fun newJob(job: Job): Long {
- synchronized(jobs) {
- val jobId = jobIdCounter
-
- jobIdCounter += 1
-
- jobs.put(jobId, job)
-
- reaperJobs.put(
- jobId,
- GlobalScope.launch(Dispatchers.Default) {
- job.join()
-
- synchronized(jobs) { jobs.remove(jobId) }
- }
- )
-
- return jobId
- }
- }
-
- fun newJob(name: String, job: Job): Long {
- synchronized(namedJobs) {
- cancelJob(name)
-
- val newJobId = newJob(job)
-
- namedJobs.put(name, newJobId)
-
- return newJobId
- }
- }
-
- fun newBackgroundJob(name: String, jobBody: suspend () -> Unit): Long {
- return newJob(name, GlobalScope.launch(Dispatchers.Default) { jobBody() })
- }
-
- fun newUiJob(name: String, jobBody: suspend () -> Unit): Long {
- return newJob(name, GlobalScope.launch(Dispatchers.Main) { jobBody() })
- }
-
- suspend fun <T> runOnBackground(jobBody: suspend () -> T): T {
- val job = GlobalScope.async(Dispatchers.Default) { jobBody() }
-
- newJob(job)
-
- return job.await()
- }
-
- fun cancelJob(name: String) {
- synchronized(namedJobs) { namedJobs.remove(name)?.let { oldJobId -> cancelJob(oldJobId) } }
- }
-
- fun cancelJob(jobId: Long) {
- synchronized(jobs) {
- jobs.remove(jobId)?.cancel()
- reaperJobs.remove(jobId)?.cancel()
- }
- }
-
- fun cancelAllJobs() {
- synchronized(jobs) {
- for (job in jobs.values) {
- job.cancel()
- }
-
- for (job in reaperJobs.values) {
- job.cancel()
- }
-
- jobs.clear()
- reaperJobs.clear()
- namedJobs.clear()
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/LocationConstraintExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/LocationConstraintExtensions.kt
deleted file mode 100644
index c96a8b8247..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/LocationConstraintExtensions.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.LocationConstraint
-
-fun LocationConstraint.toGeographicLocationConstraint(): GeographicLocationConstraint? =
- when (this) {
- is LocationConstraint.Location -> this.location
- is LocationConstraint.CustomList -> null
- }
-
-fun Constraint<LocationConstraint>.toGeographicLocationConstraint():
- Constraint<GeographicLocationConstraint> =
- when (this) {
- is Constraint.Only ->
- when (value) {
- is LocationConstraint.Location ->
- Constraint.Only((value as LocationConstraint.Location).location)
- is LocationConstraint.CustomList -> Constraint.Any()
- }
- is Constraint.Any -> Constraint.Any()
- }
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
deleted file mode 100644
index a2bd193b2f..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/MessengerExtensions.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-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.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.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/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt
index 4035f5b182..45ee4a2a17 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt
@@ -4,6 +4,7 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
class SmartDeferred<T>(private val deferred: Deferred<T>) {
private val jobTracker = JobTracker()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt
deleted file mode 100644
index 4fe7da3a8b..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import net.mullvad.mullvadvpn.BuildConfig
-import net.mullvad.mullvadvpn.constant.BuildTypes
-import org.joda.time.DateTime
-import org.joda.time.format.DateTimeFormat
-
-private const val EXPIRY_FORMAT = "YYYY-MM-dd HH:mm:ss z"
-
-fun String.capitalizeFirstCharOfEachWord(): String {
- return split(" ")
- .joinToString(" ") { word -> word.replaceFirstChar { firstChar -> firstChar.uppercase() } }
- .trimEnd()
-}
-
-fun String.parseAsDateTime(): DateTime? {
- return try {
- DateTime.parse(this, DateTimeFormat.forPattern(EXPIRY_FORMAT))
- } catch (ex: Exception) {
- null
- }
-}
-
-fun String.appendHideNavOnReleaseBuild(): String =
- if (BuildTypes.RELEASE == BuildConfig.BUILD_TYPE) {
- "$this?hide_nav"
- } else {
- this
- }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt
index 0e4a115ed6..88337aea00 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt
@@ -20,11 +20,11 @@ import kotlinx.coroutines.withTimeoutOrNull
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.state.DeviceListItemUiState
import net.mullvad.mullvadvpn.compose.state.DeviceListUiState
+import net.mullvad.mullvadvpn.lib.common.util.parseAsDateTime
import net.mullvad.mullvadvpn.model.Device
import net.mullvad.mullvadvpn.model.DeviceList
import net.mullvad.mullvadvpn.model.RemoveDeviceResult
import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.util.parseAsDateTime
typealias DeviceId = String
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt
index 12b713838a..52bbbee0a6 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ConnectionProxyTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.service
+package net.mullvad.mullvadvpn.ui.serviceconnection
import android.os.DeadObjectException
import android.os.Looper
@@ -19,7 +19,6 @@ import kotlin.test.assertEquals
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
import org.junit.After
import org.junit.Before
import org.junit.Test
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt
index 2b492ef5cd..50f930d2a5 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/service/ServiceConnectionDeviceDataSourceTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.service
+package net.mullvad.mullvadvpn.ui.serviceconnection
import android.os.DeadObjectException
import android.os.Looper
@@ -13,11 +13,10 @@ import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlin.reflect.KClass
+import net.mullvad.mullvadvpn.lib.common.util.JobTracker
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.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