diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-02 08:49:41 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-09 11:02:02 +0200 |
| commit | 8cfcae2c7d7148d55a4812df420a0aa6a9d4d7d4 (patch) | |
| tree | 3ccef8b1ff7f1619df798289acad0427a23e7cdc /android/app/src | |
| parent | cc3d7a2295765fb47949fc73b8bcf7cb76cea552 (diff) | |
| download | mullvadvpn-8cfcae2c7d7148d55a4812df420a0aa6a9d4d7d4.tar.xz mullvadvpn-8cfcae2c7d7148d55a4812df420a0aa6a9d4d7d4.zip | |
Implement LWO on android
Diffstat (limited to 'android/app/src')
15 files changed, 92 insertions, 12 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt index 8e46a555c9..c6c69e088b 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt @@ -23,6 +23,7 @@ private val DUMMY_RELAY_1 = ownership = Ownership.Rented, daita = false, quic = null, + lwo = false, ) private val DUMMY_RELAY_2 = RelayItem.Location.Relay( @@ -36,6 +37,7 @@ private val DUMMY_RELAY_2 = ownership = Ownership.MullvadOwned, daita = false, quic = null, + lwo = false, ) private val DUMMY_RELAY_CITY_1 = RelayItem.Location.City( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt index 508d4af359..eafcb58d3d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt @@ -57,6 +57,7 @@ fun FilterRow( is FilterChip.Entry -> EntryFilterChip() is FilterChip.Exit -> ExitFilterChip() is FilterChip.Quic -> QuicFilterChip() + is FilterChip.Lwo -> LwoFilterChip() } } } @@ -116,6 +117,11 @@ fun QuicFilterChip() { ) } +@Composable +fun LwoFilterChip() { + MullvadFilterChip(text = stringResource(id = R.string.lwo), onRemoveClick = {}, enabled = false) +} + private fun Ownership.stringResources(): Int = when (this) { Ownership.MullvadOwned -> R.string.owned diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt index bc0c52db83..8a5f94d730 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt @@ -123,6 +123,7 @@ private fun ObfuscationMode.toTitle() = ObfuscationMode.Udp2Tcp -> stringResource(id = R.string.upd_over_tcp) ObfuscationMode.Shadowsocks -> stringResource(id = R.string.shadowsocks) ObfuscationMode.Quic -> stringResource(id = R.string.quic) + ObfuscationMode.Lwo -> stringResource(id = R.string.lwo) } @Composable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt index 98abee9589..6c404a5bc9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt @@ -131,7 +131,8 @@ private fun FeatureIndicator.text(): String { FeatureIndicator.SPLIT_TUNNELING -> R.string.split_tunneling FeatureIndicator.SHADOWSOCKS, FeatureIndicator.UDP_2_TCP, - FeatureIndicator.QUIC -> R.string.feature_obfuscation + FeatureIndicator.QUIC, + FeatureIndicator.LWO -> R.string.feature_obfuscation FeatureIndicator.LAN_SHARING -> R.string.local_network_sharing FeatureIndicator.DNS_CONTENT_BLOCKERS -> R.string.dns_content_blockers FeatureIndicator.CUSTOM_DNS -> R.string.feature_custom_dns diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt index 13b6a518cf..c7074b10a6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt @@ -807,5 +807,6 @@ private fun FeatureIndicator.destination() = FeatureIndicator.DNS_CONTENT_BLOCKERS, FeatureIndicator.CUSTOM_DNS, FeatureIndicator.CUSTOM_MTU, - FeatureIndicator.QUIC -> VpnSettingsDestination(scrollToFeature = this, isModal = true) + FeatureIndicator.QUIC, + FeatureIndicator.LWO -> VpnSettingsDestination(scrollToFeature = this, isModal = true) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt index a997b39ad4..1cba4093f1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt @@ -121,6 +121,7 @@ import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_ import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG +import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_SHADOWSOCKS_CELL_TEST_TAG @@ -479,7 +480,8 @@ fun VpnSettingsContent( when (initialScrollToFeature) { FeatureIndicator.UDP_2_TCP, FeatureIndicator.SHADOWSOCKS, - FeatureIndicator.QUIC -> VpnSettingItem.ObfuscationHeader::class + FeatureIndicator.QUIC, + FeatureIndicator.LWO -> VpnSettingItem.ObfuscationHeader::class FeatureIndicator.LAN_SHARING -> VpnSettingItem.LocalNetworkSharingSetting::class FeatureIndicator.QUANTUM_RESISTANCE -> VpnSettingItem.QuantumResistanceHeader::class FeatureIndicator.DNS_CONTENT_BLOCKERS -> VpnSettingItem.DnsContentBlockersHeader::class @@ -867,6 +869,17 @@ fun VpnSettingsContent( ) } + is VpnSettingItem.ObfuscationItem.Lwo -> + item(key = it::class.simpleName) { + SelectableCell( + title = stringResource(id = R.string.lwo), + isSelected = it.selected, + modifier = Modifier.animateItem(), + testTag = WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG, + onCellClicked = { onSelectObfuscationMode(ObfuscationMode.Lwo) }, + ) + } + is VpnSettingItem.QuantumItem -> item(key = it::class.simpleName + it.quantumResistantState) { SelectableCell( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt index 3ce6637115..fe3ee1a50a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt @@ -106,6 +106,8 @@ sealed interface VpnSettingItem { data class Quic(override val selected: Boolean) : ObfuscationItem + data class Lwo(override val selected: Boolean) : ObfuscationItem + data class Off(override val selected: Boolean) : ObfuscationItem } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt index 08fafc56c0..d860aa3c21 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt @@ -209,6 +209,8 @@ data class VpnSettingsUiState(val settings: List<VpnSettingItem>, val isModal: B VpnSettingItem.ObfuscationItem.Quic(obfuscationMode == ObfuscationMode.Quic) ) add(VpnSettingItem.Divider) + add(VpnSettingItem.ObfuscationItem.Lwo(obfuscationMode == ObfuscationMode.Lwo)) + add(VpnSettingItem.Divider) add(VpnSettingItem.ObfuscationItem.Off(obfuscationMode == ObfuscationMode.Off)) add(VpnSettingItem.Spacer) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt index c59b0124c3..182044b6fc 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt @@ -60,17 +60,18 @@ fun RelayItem.CustomList.filter( providers: Constraint<Providers>, daita: Boolean, quic: Boolean, + lwo: Boolean, ipVersion: Constraint<IpVersion>, ): RelayItem.CustomList { val newLocations = locations.mapNotNull { when (it) { is RelayItem.Location.Country -> - it.filter(ownership, providers, daita, quic, ipVersion) + it.filter(ownership, providers, daita, quic, lwo, ipVersion) is RelayItem.Location.City -> - it.filter(ownership, providers, daita, quic, ipVersion) + it.filter(ownership, providers, daita, quic, lwo, ipVersion) is RelayItem.Location.Relay -> - it.filter(ownership, providers, daita, quic, ipVersion) + it.filter(ownership, providers, daita, quic, lwo, ipVersion) } } return copy(locations = newLocations) @@ -81,9 +82,10 @@ fun RelayItem.Location.Country.filter( providers: Constraint<Providers>, daita: Boolean, quic: Boolean, + lwo: Boolean, ipVersion: Constraint<IpVersion>, ): RelayItem.Location.Country? { - val cities = cities.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) } + val cities = cities.mapNotNull { it.filter(ownership, providers, daita, quic, lwo, ipVersion) } return if (cities.isNotEmpty()) { this.copy(cities = cities) } else { @@ -96,9 +98,10 @@ private fun RelayItem.Location.City.filter( providers: Constraint<Providers>, daita: Boolean, quic: Boolean, + lwo: Boolean, ipVersion: Constraint<IpVersion>, ): RelayItem.Location.City? { - val relays = relays.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) } + val relays = relays.mapNotNull { it.filter(ownership, providers, daita, quic, lwo, ipVersion) } return if (relays.isNotEmpty()) { this.copy(relays = relays) } else { @@ -109,12 +112,16 @@ private fun RelayItem.Location.City.filter( private fun RelayItem.Location.Relay.requiredFeatures( requireDaita: Boolean, requireQuic: Boolean, + requireLwo: Boolean, ipVersion: Constraint<IpVersion>, ): Boolean = when { + // Can not require LWO and require QUIC at the same time requireDaita && requireQuic -> daita && quic?.supports(ipVersion) == true + requireDaita && requireLwo -> daita && lwo requireDaita -> daita requireQuic -> quic?.supports(ipVersion) == true + requireLwo -> lwo else -> true } @@ -130,10 +137,11 @@ private fun RelayItem.Location.Relay.filter( providers: Constraint<Providers>, daita: Boolean, quic: Boolean, + lwo: Boolean, ipVersion: Constraint<IpVersion>, ): RelayItem.Location.Relay? = if ( - requiredFeatures(daita, quic, ipVersion) && + requiredFeatures(daita, quic, lwo, ipVersion) && hasOwnership(ownership) && hasProvider(providers) ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt index 12ec3a296c..c115d86b2a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt @@ -11,8 +11,10 @@ import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isLwoEnabled import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByLwo import net.mullvad.mullvadvpn.util.shouldFilterByQuic typealias ModelOwnership = Ownership @@ -84,6 +86,11 @@ class FilterChipUseCase( ) { add(FilterChip.Quic) } + if ( + shouldFilterByLwo(settings?.isLwoEnabled() == true, relayListType = relayListType) + ) { + add(FilterChip.Lwo) + } } } } @@ -100,4 +107,6 @@ sealed interface FilterChip { data object Exit : FilterChip data object Quic : FilterChip + + data object Lwo : FilterChip } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt index 2681cfa3ad..eb41e3b918 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt @@ -14,8 +14,10 @@ import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.util.ipVersionConstraint import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isLwoEnabled import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByLwo import net.mullvad.mullvadvpn.util.shouldFilterByQuic class FilteredRelayListUseCase( @@ -42,7 +44,12 @@ class FilteredRelayListUseCase( ), shouldFilterByQuic = shouldFilterByQuic( - settings?.isQuicEnabled() == true, + isQuicEnabled = settings?.isQuicEnabled() == true, + relayListType = relayListType, + ), + shouldFilterByLwo = + shouldFilterByLwo( + isLwoEnable = settings?.isLwoEnabled() == true, relayListType = relayListType, ), constraintIpVersion = settings?.ipVersionConstraint() ?: Constraint.Any, @@ -54,6 +61,7 @@ class FilteredRelayListUseCase( providers: Constraint<Providers>, shouldFilterByDaita: Boolean, shouldFilterByQuic: Boolean, + shouldFilterByLwo: Boolean, constraintIpVersion: Constraint<IpVersion>, ) = mapNotNull { it.filter( @@ -61,6 +69,7 @@ class FilteredRelayListUseCase( providers, shouldFilterByDaita, shouldFilterByQuic, + shouldFilterByLwo, constraintIpVersion, ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt index 6604a76805..d351b7633c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt @@ -14,8 +14,10 @@ import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.util.ipVersionConstraint import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isLwoEnabled import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByLwo import net.mullvad.mullvadvpn.util.shouldFilterByQuic class FilterCustomListsRelayItemUseCase( @@ -43,7 +45,12 @@ class FilterCustomListsRelayItemUseCase( ), quic = shouldFilterByQuic( - settings?.isQuicEnabled() == true, + isQuicEnabled = settings?.isQuicEnabled() == true, + relayListType = relayListType, + ), + lwo = + shouldFilterByLwo( + isLwoEnable = settings?.isLwoEnabled() == true, relayListType = relayListType, ), ipVersion = settings?.ipVersionConstraint() ?: Constraint.Any, @@ -55,8 +62,16 @@ class FilterCustomListsRelayItemUseCase( providers: Constraint<Providers>, daita: Boolean, quic: Boolean, + lwo: Boolean, ipVersion: Constraint<IpVersion>, ) = mapNotNull { - it.filter(ownership, providers, daita = daita, quic = quic, ipVersion = ipVersion) + it.filter( + ownership, + providers, + daita = daita, + quic = quic, + lwo = lwo, + ipVersion = ipVersion, + ) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt index 4fa7ba231c..32dabab1c0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt @@ -16,3 +16,10 @@ fun shouldFilterByQuic(isQuicEnabled: Boolean, relayListType: RelayListType) = is RelayListType.Multihop -> isQuicEnabled && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY } + +fun shouldFilterByLwo(isLwoEnable: Boolean, relayListType: RelayListType) = + when (relayListType) { + RelayListType.Single -> isLwoEnable + is RelayListType.Multihop -> + isLwoEnable && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt index 4da463ab51..45354bad21 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt @@ -22,6 +22,8 @@ fun Settings.isDaitaAndDirectOnly() = isDaitaEnabled() && isDaitaDirectOnly() fun Settings.isQuicEnabled() = obfuscationSettings.selectedObfuscationMode == ObfuscationMode.Quic +fun Settings.isLwoEnabled() = obfuscationSettings.selectedObfuscationMode == ObfuscationMode.Lwo + fun Settings.ipVersionConstraint() = relaySettings.relayConstraints.wireguardConstraints.ipVersion fun Settings.isDaitaEnabled() = daitaSettings().enabled diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt index fc6dcf79c0..100c665781 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt @@ -365,6 +365,7 @@ class CustomListLocationsViewModelTest { ownership = Ownership.MullvadOwned, daita = false, quic = null, + lwo = false, ) ), ) @@ -383,6 +384,7 @@ class CustomListLocationsViewModelTest { ownership = Ownership.MullvadOwned, daita = false, quic = null, + lwo = false, ) } } |
