diff options
| author | Albin <albin@mullvad.net> | 2023-07-28 11:17:05 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-07-28 11:17:05 +0200 |
| commit | 6ea45204b49571fea6e8cbbf2a3f49b2818278fd (patch) | |
| tree | 606b8625cbf4454f7ecbf659d77ec7a26c14a113 /android/app/src/main | |
| parent | 7a1c9dba446651f06b00c80178407b34120cede9 (diff) | |
| parent | 463fca04ab97e09daa9d1de7ee7ffdebf93ad023 (diff) | |
| download | mullvadvpn-6ea45204b49571fea6e8cbbf2a3f49b2818278fd.tar.xz mullvadvpn-6ea45204b49571fea6e8cbbf2a3f49b2818278fd.zip | |
Merge branch 'move-vpn-service-to-its-own-module'
Diffstat (limited to 'android/app/src/main')
58 files changed, 37 insertions, 2962 deletions
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 |
