summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2024-08-26 15:48:37 +0200
committerAlbin <albin@mullvad.net>2024-09-06 14:59:55 +0200
commit3847c1eb82a324a4b5fff2a5e076750ebf4449c4 (patch)
tree55c45a4a75e56503e675fdad5418cbeafb850854 /android
parentd464325f98bc488f091ef18b4ba04e0d7dbe2605 (diff)
downloadmullvadvpn-3847c1eb82a324a4b5fff2a5e076750ebf4449c4.tar.xz
mullvadvpn-3847c1eb82a324a4b5fff2a5e076750ebf4449c4.zip
Add daita grpc and ui
Diffstat (limited to 'android')
-rw-r--r--android/app/build.gradle.kts10
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt12
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt37
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt50
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt101
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaInfoDialog.kt35
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/RelayItemPreviewData.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/TunnelStatePreviewData.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt34
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt31
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt18
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt1
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt7
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt7
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt10
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt14
-rw-r--r--android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt20
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDaitaSettingsError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardRelayEndpointData.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt6
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml6
-rw-r--r--android/lib/resource/src/main/res/values/strings_non_translatable.xml2
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt23
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt18
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ContextExtensions.kt23
40 files changed, 467 insertions, 84 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index f8c92c9849..f5e4c22d6b 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -20,6 +20,7 @@ plugins {
val repoRootPath = rootProject.projectDir.absoluteFile.parentFile.absolutePath
val extraAssetsDirectory = "${project.buildDir}/extraAssets"
val relayListPath = "$extraAssetsDirectory/relays.json"
+val maybenotMachinesDirectory = "$extraAssetsDirectory/maybenot_machines"
val defaultChangelogAssetsDirectory = "$repoRootPath/android/src/main/play/release-notes/"
val extraJniDirectory = "${project.buildDir}/extraJni"
@@ -239,6 +240,7 @@ android {
// Ensure all relevant assemble tasks depend on our ensure tasks.
tasks.get("assemble$capitalizedVariantName").apply {
dependsOn(tasks.get("ensureRelayListExist"))
+ dependsOn(tasks.get("ensureMaybenotMachinesExist"))
dependsOn(tasks.get("ensureJniDirectoryExist"))
dependsOn(tasks.get("ensureValidVersionCode"))
}
@@ -283,6 +285,14 @@ tasks.register("ensureRelayListExist") {
}
}
+tasks.register("ensureMaybenotMachinesExist") {
+ doLast {
+ if (!file(maybenotMachinesDirectory).exists()) {
+ throw GradleException("Missing maybenot machines: $maybenotMachinesDirectory")
+ }
+ }
+}
+
tasks.register("ensureJniDirectoryExist") {
doLast {
if (!file(extraJniDirectory).exists()) {
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 3cea5e5c68..a073bc60ff 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
@@ -22,6 +22,7 @@ private val DUMMY_RELAY_1 =
active = true,
provider =
Provider(providerId = ProviderId("PROVIDER RENTED"), ownership = Ownership.Rented),
+ daita = false,
)
private val DUMMY_RELAY_2 =
RelayItem.Location.Relay(
@@ -33,6 +34,7 @@ private val DUMMY_RELAY_2 =
active = true,
provider =
Provider(providerId = ProviderId("PROVIDER OWNED"), ownership = Ownership.MullvadOwned),
+ daita = 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 a94846dacf..d3e233c67b 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.Ownership ->
OwnershipFilterChip(it.ownership, onRemoveOwnershipFilter)
is FilterChip.Provider -> ProviderFilterChip(it.count, onRemoveProviderFilter)
+ is FilterChip.Daita -> DaitaFilterChip()
}
}
}
@@ -67,6 +68,7 @@ fun ProviderFilterChip(providers: Int, onRemoveClick: () -> Unit) {
MullvadFilterChip(
text = stringResource(id = R.string.number_of_providers, providers),
onRemoveClick = onRemoveClick,
+ enabled = true,
)
}
@@ -75,6 +77,16 @@ fun OwnershipFilterChip(ownership: Ownership, onRemoveClick: () -> Unit) {
MullvadFilterChip(
text = stringResource(ownership.stringResources()),
onRemoveClick = onRemoveClick,
+ enabled = true,
+ )
+}
+
+@Composable
+fun DaitaFilterChip() {
+ MullvadFilterChip(
+ text = stringResource(id = R.string.setting_chip, stringResource(id = R.string.daita)),
+ onRemoveClick = {},
+ enabled = false,
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
index 4761c15c9d..94e1c7853a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
@@ -19,11 +19,24 @@ import net.mullvad.mullvadvpn.lib.theme.shape.chipShape
@Preview
@Composable
-private fun PreviewMullvadFilterChip() {
+private fun PreviewEnabledMullvadFilterChip() {
AppTheme {
MullvadFilterChip(
text = stringResource(id = R.string.number_of_providers),
onRemoveClick = {},
+ enabled = true,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewDisabledMullvadFilterChip() {
+ AppTheme {
+ MullvadFilterChip(
+ text = stringResource(id = R.string.number_of_providers),
+ onRemoveClick = {},
+ enabled = false,
)
}
}
@@ -36,30 +49,38 @@ fun MullvadFilterChip(
iconColor: Color = MaterialTheme.colorScheme.onPrimary,
text: String,
onRemoveClick: () -> Unit,
+ enabled: Boolean,
) {
InputChip(
+ enabled = enabled,
shape = MaterialTheme.shapes.chipShape,
colors =
FilterChipDefaults.filterChipColors(
containerColor = containerColor,
+ disabledContainerColor = containerColor,
labelColor = labelColor,
+ disabledLabelColor = labelColor,
iconColor = iconColor,
),
border =
FilterChipDefaults.filterChipBorder(
borderColor = borderColor,
+ disabledBorderColor = borderColor,
enabled = true,
selected = false,
),
selected = false,
onClick = onRemoveClick,
label = { Text(text = text, style = MaterialTheme.typography.labelMedium) },
- trailingIcon = {
- Icon(
- painter = painterResource(id = R.drawable.icon_close),
- contentDescription = null,
- modifier = Modifier.size(Dimens.smallIconSize),
- )
- },
+ trailingIcon =
+ if (enabled) {
+ {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_close),
+ contentDescription = null,
+ modifier = Modifier.size(Dimens.smallIconSize),
+ )
+ }
+ } else null,
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt
index f9c0f26862..3fbd40c537 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt
@@ -33,6 +33,7 @@ private fun PreviewLocationInfo() {
isVisible = true,
isExpanded = true,
location = null,
+ isUsingDaita = false,
inAddress = null,
outAddress = "",
)
@@ -48,6 +49,7 @@ fun LocationInfo(
isVisible: Boolean,
isExpanded: Boolean,
location: GeoIpLocation?,
+ isUsingDaita: Boolean,
inAddress: Triple<String, Int, TransportProtocol>?,
outAddress: String,
) {
@@ -61,15 +63,12 @@ fun LocationInfo(
.then(modifier)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- text = location?.hostname ?: "",
- color =
- if (isExpanded) {
- colorExpanded
- } else {
- colorCollapsed
- },
- style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold),
+ RelayHostname(
+ hostname = location?.hostname,
+ isUsingDaita = isUsingDaita,
+ isExpanded = isExpanded,
+ colorExpanded = colorExpanded,
+ colorCollapsed = colorCollapsed,
)
Chevron(
isExpanded = isExpanded,
@@ -119,3 +118,36 @@ fun LocationInfo(
)
}
}
+
+@Composable
+private fun RelayHostname(
+ hostname: String?,
+ isUsingDaita: Boolean,
+ isExpanded: Boolean,
+ colorExpanded: Color,
+ colorCollapsed: Color,
+) {
+ val hostnameTitle =
+ when {
+ hostname != null && isUsingDaita -> {
+ stringResource(
+ id = R.string.connected_using_daita,
+ hostname,
+ stringResource(id = R.string.daita),
+ )
+ }
+ hostname != null -> hostname
+ else -> ""
+ }
+
+ Text(
+ text = hostnameTitle,
+ color =
+ if (isExpanded) {
+ colorExpanded
+ } else {
+ colorCollapsed
+ },
+ style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold),
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt
new file mode 100644
index 0000000000..a5237a0ca5
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaConfirmationDialog.kt
@@ -0,0 +1,101 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.dropUnlessResumed
+import com.ramcosta.composedestinations.annotation.Destination
+import com.ramcosta.composedestinations.annotation.RootGraph
+import com.ramcosta.composedestinations.result.EmptyResultBackNavigator
+import com.ramcosta.composedestinations.result.ResultBackNavigator
+import com.ramcosta.composedestinations.spec.DestinationStyle
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.button.PrimaryButton
+import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.lib.theme.color.AlphaScrollbar
+
+@Preview
+@Composable
+private fun PreviewDaitaConfirmationDialog() {
+ AppTheme { DaitaConfirmation(EmptyResultBackNavigator()) }
+}
+
+@Destination<RootGraph>(style = DestinationStyle.Dialog::class)
+@Composable
+fun DaitaConfirmation(navigator: ResultBackNavigator<Boolean>) {
+ AlertDialog(
+ onDismissRequest = dropUnlessResumed { navigator.navigateBack(false) },
+ icon = {
+ Icon(
+ modifier = Modifier.fillMaxWidth().height(Dimens.dialogIconHeight),
+ painter = painterResource(id = R.drawable.icon_alert),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurface,
+ )
+ },
+ text = {
+ val scrollState = rememberScrollState()
+ Column(
+ Modifier.drawVerticalScrollbar(
+ scrollState,
+ MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaScrollbar),
+ )
+ .verticalScroll(scrollState),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(id = R.string.daita_relay_subset_warning),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodySmall,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(modifier = Modifier.height(Dimens.verticalSpace))
+
+ Text(
+ text =
+ stringResource(
+ id = R.string.daita_warning,
+ stringResource(id = R.string.daita),
+ ),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodySmall,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ },
+ confirmButton = {
+ Column(verticalArrangement = Arrangement.spacedBy(Dimens.buttonSpacing)) {
+ PrimaryButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.enable_anyway),
+ onClick = { navigator.navigateBack(true) },
+ )
+
+ PrimaryButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.back),
+ onClick = dropUnlessResumed { navigator.navigateBack(false) },
+ )
+ }
+ },
+ containerColor = MaterialTheme.colorScheme.surface,
+ titleContentColor = MaterialTheme.colorScheme.onSurface,
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaInfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaInfoDialog.kt
new file mode 100644
index 0000000000..a7e2b0b78e
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DaitaInfoDialog.kt
@@ -0,0 +1,35 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.dropUnlessResumed
+import com.ramcosta.composedestinations.annotation.Destination
+import com.ramcosta.composedestinations.annotation.RootGraph
+import com.ramcosta.composedestinations.navigation.DestinationsNavigator
+import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
+import com.ramcosta.composedestinations.spec.DestinationStyle
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+
+@Preview
+@Composable
+private fun PreviewDaitaInfoDialog() {
+ AppTheme { DaitaInfo(EmptyDestinationsNavigator) }
+}
+
+@Destination<RootGraph>(style = DestinationStyle.Dialog::class)
+@Composable
+fun DaitaInfo(navigator: DestinationsNavigator) {
+ InfoDialog(
+ message =
+ stringResource(
+ id = R.string.daita_info,
+ stringResource(id = R.string.daita),
+ stringResource(id = R.string.daita_full),
+ ),
+ additionalInfo =
+ stringResource(id = R.string.daita_warning, stringResource(id = R.string.daita)),
+ onDismiss = dropUnlessResumed { navigator.navigateUp() },
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/RelayItemPreviewData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/RelayItemPreviewData.kt
index 70d707c10c..0af4199f40 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/RelayItemPreviewData.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/RelayItemPreviewData.kt
@@ -51,11 +51,13 @@ private fun generateRelayItemRelay(
cityCode: GeoLocationId.City,
hostName: String,
active: Boolean = true,
+ daita: Boolean = true,
) =
RelayItem.Location.Relay(
id = GeoLocationId.Hostname(city = cityCode, code = hostName),
active = active,
provider = Provider(ProviderId("Provider"), Ownership.MullvadOwned),
+ daita = daita,
)
private fun String.generateCountryCode() =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/TunnelStatePreviewData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/TunnelStatePreviewData.kt
index a8981e612e..b6ca18c91a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/TunnelStatePreviewData.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/TunnelStatePreviewData.kt
@@ -18,14 +18,14 @@ object TunnelStatePreviewData {
fun generateConnectingState(featureIndicators: Int, quantumResistant: Boolean) =
TunnelState.Connecting(
- endpoint = generateTunnelEndpoint(quantumResistant = quantumResistant),
+ endpoint = generateTunnelEndpoint(quantumResistant = quantumResistant, daita = false),
location = generateLocation(),
featureIndicators = generateFeatureIndicators(featureIndicators),
)
fun generateConnectedState(featureIndicators: Int, quantumResistant: Boolean) =
TunnelState.Connected(
- endpoint = generateTunnelEndpoint(quantumResistant = quantumResistant),
+ endpoint = generateTunnelEndpoint(quantumResistant = quantumResistant, daita = true),
location = generateLocation(),
featureIndicators = generateFeatureIndicators(featureIndicators),
)
@@ -39,7 +39,7 @@ object TunnelStatePreviewData {
)
}
-private fun generateTunnelEndpoint(quantumResistant: Boolean): TunnelEndpoint =
+private fun generateTunnelEndpoint(quantumResistant: Boolean, daita: Boolean): TunnelEndpoint =
TunnelEndpoint(
endpoint = generateEndpoint(TransportProtocol.Udp),
quantumResistant = quantumResistant,
@@ -48,6 +48,7 @@ private fun generateTunnelEndpoint(quantumResistant: Boolean): TunnelEndpoint =
endpoint = generateEndpoint(TransportProtocol.Tcp),
ObfuscationType.Udp2Tcp,
),
+ daita = daita,
)
private fun generateEndpoint(transportProtocol: TransportProtocol) =
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 c5b2be56ed..c9ee962eb1 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
@@ -356,6 +356,7 @@ private fun ConnectionInfo(state: ConnectUiState) {
isVisible = state.showLocationInfo,
isExpanded = expanded,
location = state.location,
+ isUsingDaita = state.tunnelState.isUsingDaita(),
inAddress = state.inAddress,
outAddress = state.outAddress,
modifier =
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 1bccbaa713..c130d6d61c 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
@@ -36,6 +36,8 @@ import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AutoConnectAndLockdownModeDestination
import com.ramcosta.composedestinations.generated.destinations.ContentBlockersInfoDestination
import com.ramcosta.composedestinations.generated.destinations.CustomDnsInfoDestination
+import com.ramcosta.composedestinations.generated.destinations.DaitaConfirmationDestination
+import com.ramcosta.composedestinations.generated.destinations.DaitaInfoDestination
import com.ramcosta.composedestinations.generated.destinations.DnsDestination
import com.ramcosta.composedestinations.generated.destinations.LocalNetworkSharingInfoDestination
import com.ramcosta.composedestinations.generated.destinations.MalwareInfoDestination
@@ -147,6 +149,7 @@ fun VpnSettings(
dnsDialogResult: ResultRecipient<DnsDestination, DnsDialogResult>,
customWgPortResult: ResultRecipient<WireguardCustomPortDestination, Port?>,
mtuDialogResult: ResultRecipient<MtuDestination, Boolean>,
+ daitaConfirmationDialogResult: ResultRecipient<DaitaConfirmationDestination, Boolean>,
) {
val vm = koinViewModel<VpnSettingsViewModel>()
val state by vm.uiState.collectAsStateWithLifecycle()
@@ -176,6 +179,12 @@ fun VpnSettings(
}
}
+ daitaConfirmationDialogResult.OnNavResultValue { doEnableDaita ->
+ if (doEnableDaita) {
+ vm.onToggleDaita(true)
+ }
+ }
+
val snackbarHostState = remember { SnackbarHostState() }
val context = LocalContext.current
CollectSideEffectWithLifecycle(vm.uiSideEffect) {
@@ -224,6 +233,9 @@ fun VpnSettings(
},
navigateToLocalNetworkSharingInfo =
dropUnlessResumed { navigator.navigate(LocalNetworkSharingInfoDestination) },
+ navigateToDaitaInfo = dropUnlessResumed { navigator.navigate(DaitaInfoDestination) },
+ navigateToDaitaConfirmation =
+ dropUnlessResumed { navigator.navigate(DaitaConfirmationDestination) },
navigateToServerIpOverrides =
dropUnlessResumed { navigator.navigate(ServerIpOverridesDestination) },
onToggleBlockTrackers = vm::onToggleBlockTrackers,
@@ -231,6 +243,7 @@ fun VpnSettings(
onToggleBlockMalware = vm::onToggleBlockMalware,
onToggleAutoConnect = vm::onToggleAutoConnect,
onToggleLocalNetworkSharing = vm::onToggleLocalNetworkSharing,
+ onDisableDaita = { vm.onToggleDaita(false) },
onToggleBlockAdultContent = vm::onToggleBlockAdultContent,
onToggleBlockGambling = vm::onToggleBlockGambling,
onToggleBlockSocialMedia = vm::onToggleBlockSocialMedia,
@@ -273,6 +286,8 @@ fun VpnSettingsScreen(
navigateUdp2TcpInfo: () -> Unit = {},
navigateToWireguardPortInfo: (availablePortRanges: List<PortRange>) -> Unit = {},
navigateToLocalNetworkSharingInfo: () -> Unit = {},
+ navigateToDaitaInfo: () -> Unit = {},
+ navigateToDaitaConfirmation: () -> Unit = {},
navigateToWireguardPortDialog: () -> Unit = {},
navigateToServerIpOverrides: () -> Unit = {},
onToggleBlockTrackers: (Boolean) -> Unit = {},
@@ -280,6 +295,7 @@ fun VpnSettingsScreen(
onToggleBlockMalware: (Boolean) -> Unit = {},
onToggleAutoConnect: (Boolean) -> Unit = {},
onToggleLocalNetworkSharing: (Boolean) -> Unit = {},
+ onDisableDaita: () -> Unit = {},
onToggleBlockAdultContent: (Boolean) -> Unit = {},
onToggleBlockGambling: (Boolean) -> Unit = {},
onToggleBlockSocialMedia: (Boolean) -> Unit = {},
@@ -497,8 +513,24 @@ fun VpnSettingsScreen(
)
}
- itemWithDivider {
+ item {
+ Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
+ HeaderSwitchComposeCell(
+ title = stringResource(id = R.string.daita),
+ isToggled = state.isDaitaEnabled,
+ onCellClicked = { enable ->
+ if (enable) {
+ navigateToDaitaConfirmation()
+ } else {
+ onDisableDaita()
+ }
+ },
+ onInfoClicked = navigateToDaitaInfo,
+ )
Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
+ }
+
+ itemWithDivider {
InformationComposeCell(
title = stringResource(id = R.string.wireguard_port_title),
onInfoClicked = { navigateToWireguardPortInfo(state.availablePortRanges) },
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
index 4fa2fbb1eb..d8245792a3 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
@@ -22,6 +22,8 @@ sealed interface FilterChip {
data class Ownership(val ownership: ModelOwnership) : FilterChip
data class Provider(val count: Int) : FilterChip
+
+ data object Daita : FilterChip
}
enum class RelayListItemContentType {
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 0eaf1695a7..b57de21bc5 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
@@ -13,6 +13,7 @@ data class VpnSettingsUiState(
val mtu: Mtu?,
val isAutoConnectEnabled: Boolean,
val isLocalNetworkSharingEnabled: Boolean,
+ val isDaitaEnabled: Boolean,
val isCustomDnsEnabled: Boolean,
val customDnsItems: List<CustomDnsItem>,
val contentBlockersOptions: DefaultDnsOptions,
@@ -31,6 +32,7 @@ data class VpnSettingsUiState(
mtu: Mtu? = null,
isAutoConnectEnabled: Boolean = false,
isLocalNetworkSharingEnabled: Boolean = false,
+ isDaitaEnabled: Boolean = false,
isCustomDnsEnabled: Boolean = false,
customDnsItems: List<CustomDnsItem> = emptyList(),
contentBlockersOptions: DefaultDnsOptions = DefaultDnsOptions(),
@@ -46,6 +48,7 @@ data class VpnSettingsUiState(
mtu,
isAutoConnectEnabled,
isLocalNetworkSharingEnabled,
+ isDaitaEnabled,
isCustomDnsEnabled,
customDnsItems,
contentBlockersOptions,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 1f1a489684..6b909d394d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -134,10 +134,10 @@ val uiModule = module {
single { CustomListActionUseCase(get(), get()) }
single { SelectedLocationTitleUseCase(get(), get()) }
single { AvailableProvidersUseCase(get()) }
- single { FilterCustomListsRelayItemUseCase(get(), get()) }
+ single { FilterCustomListsRelayItemUseCase(get(), get(), get()) }
single { CustomListsRelayItemUseCase(get(), get()) }
single { CustomListRelayItemsUseCase(get(), get()) }
- single { FilteredRelayListUseCase(get(), get()) }
+ single { FilteredRelayListUseCase(get(), get(), get()) }
single { LastKnownLocationUseCase(get()) }
single { InAppNotificationController(get(), get(), get(), get(), MainScope()) }
@@ -184,7 +184,9 @@ val uiModule = module {
viewModel { DnsDialogViewModel(get(), get(), get()) }
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { PrivacyDisclaimerViewModel(get(), IS_PLAY_BUILD) }
- viewModel { SelectLocationViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
+ viewModel {
+ SelectLocationViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get())
+ }
viewModel { SettingsViewModel(get(), get(), IS_PLAY_BUILD) }
viewModel { SplashViewModel(get(), get(), get(), get()) }
viewModel { VoucherDialogViewModel(get()) }
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 c87a9548c5..5d6e48a3f7 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
@@ -53,27 +53,28 @@ private fun RelayItem.Location.hasProvider(providersConstraint: Constraint<Provi
true
}
-fun RelayItem.CustomList.filterOnOwnershipAndProvider(
+fun RelayItem.CustomList.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
+ isDaitaEnabled: Boolean,
): RelayItem.CustomList {
val newLocations =
locations.mapNotNull {
when (it) {
- is RelayItem.Location.Country ->
- it.filterOnOwnershipAndProvider(ownership, providers)
- is RelayItem.Location.City -> it.filterOnOwnershipAndProvider(ownership, providers)
- is RelayItem.Location.Relay -> it.filterOnOwnershipAndProvider(ownership, providers)
+ is RelayItem.Location.Country -> it.filter(ownership, providers, isDaitaEnabled)
+ is RelayItem.Location.City -> it.filter(ownership, providers, isDaitaEnabled)
+ is RelayItem.Location.Relay -> it.filter(ownership, providers, isDaitaEnabled)
}
}
return copy(locations = newLocations)
}
-fun RelayItem.Location.Country.filterOnOwnershipAndProvider(
+fun RelayItem.Location.Country.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
+ isDaitaEnabled: Boolean,
): RelayItem.Location.Country? {
- val cities = cities.mapNotNull { it.filterOnOwnershipAndProvider(ownership, providers) }
+ val cities = cities.mapNotNull { it.filter(ownership, providers, isDaitaEnabled) }
return if (cities.isNotEmpty()) {
this.copy(cities = cities)
} else {
@@ -81,11 +82,12 @@ fun RelayItem.Location.Country.filterOnOwnershipAndProvider(
}
}
-private fun RelayItem.Location.City.filterOnOwnershipAndProvider(
+private fun RelayItem.Location.City.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
+ isDaitaEnabled: Boolean,
): RelayItem.Location.City? {
- val relays = relays.mapNotNull { it.filterOnOwnershipAndProvider(ownership, providers) }
+ val relays = relays.mapNotNull { it.filter(ownership, providers, isDaitaEnabled) }
return if (relays.isNotEmpty()) {
this.copy(relays = relays)
} else {
@@ -93,11 +95,18 @@ private fun RelayItem.Location.City.filterOnOwnershipAndProvider(
}
}
-private fun RelayItem.Location.Relay.filterOnOwnershipAndProvider(
+private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(isDaitaEnabled: Boolean): Boolean {
+ return if (isDaitaEnabled) daita else true
+}
+
+private fun RelayItem.Location.Relay.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
+ isDaitaEnabled: Boolean,
): RelayItem.Location.Relay? {
- return if (hasOwnership(ownership) && hasProvider(providers)) {
+ return if (
+ hasMatchingDaitaSetting(isDaitaEnabled) && hasOwnership(ownership) && hasProvider(providers)
+ ) {
this
} else {
null
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
index 19e52b9790..d66d4a5c00 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
@@ -68,4 +68,6 @@ class SettingsRepository(
suspend fun setLocalNetworkSharing(isEnabled: Boolean) =
managementService.setAllowLan(isEnabled)
+
+ suspend fun setDaitaEnabled(enabled: Boolean) = managementService.setDaitaEnabled(enabled)
}
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 9c1994e075..60de94946f 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
@@ -5,25 +5,33 @@ import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.Ownership
import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.RelayItem
-import net.mullvad.mullvadvpn.relaylist.filterOnOwnershipAndProvider
+import net.mullvad.mullvadvpn.relaylist.filter
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.RelayListRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
class FilteredRelayListUseCase(
private val relayListRepository: RelayListRepository,
private val relayListFilterRepository: RelayListFilterRepository,
+ private val settingsRepository: SettingsRepository,
) {
operator fun invoke() =
combine(
relayListRepository.relayList,
relayListFilterRepository.selectedOwnership,
relayListFilterRepository.selectedProviders,
- ) { relayList, selectedOwnership, selectedProviders ->
- relayList.filterOnOwnershipAndProvider(selectedOwnership, selectedProviders)
+ settingsRepository.settingsUpdates,
+ ) { relayList, selectedOwnership, selectedProviders, settings ->
+ relayList.filter(
+ selectedOwnership,
+ selectedProviders,
+ isDaitaEnabled = settings?.isDaitaEnabled() ?: false,
+ )
}
- private fun List<RelayItem.Location.Country>.filterOnOwnershipAndProvider(
+ private fun List<RelayItem.Location.Country>.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
- ) = mapNotNull { it.filterOnOwnershipAndProvider(ownership, providers) }
+ isDaitaEnabled: Boolean,
+ ) = mapNotNull { it.filter(ownership, providers, isDaitaEnabled) }
}
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 49c8ec89f6..17ead75d2a 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
@@ -6,12 +6,14 @@ import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.Ownership
import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.RelayItem
-import net.mullvad.mullvadvpn.relaylist.filterOnOwnershipAndProvider
+import net.mullvad.mullvadvpn.relaylist.filter
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
class FilterCustomListsRelayItemUseCase(
private val customListsRelayItemUseCase: CustomListsRelayItemUseCase,
private val relayListFilterRepository: RelayListFilterRepository,
+ private val settingsRepository: SettingsRepository,
) {
operator fun invoke() =
@@ -19,12 +21,18 @@ class FilterCustomListsRelayItemUseCase(
customListsRelayItemUseCase(),
relayListFilterRepository.selectedOwnership,
relayListFilterRepository.selectedProviders,
- ) { customLists, selectedOwnership, selectedProviders ->
- customLists.filterOnOwnershipAndProvider(selectedOwnership, selectedProviders)
+ settingsRepository.settingsUpdates,
+ ) { customLists, selectedOwnership, selectedProviders, settings ->
+ customLists.filterOnOwnershipAndProvider(
+ selectedOwnership,
+ selectedProviders,
+ isDaitaEnabled = settings?.isDaitaEnabled() ?: false,
+ )
}
private fun List<RelayItem.CustomList>.filterOnOwnershipAndProvider(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
- ) = mapNotNull { it.filterOnOwnershipAndProvider(ownership, providers) }
+ isDaitaEnabled: Boolean,
+ ) = mapNotNull { it.filter(ownership, providers, isDaitaEnabled = isDaitaEnabled) }
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
index 04476ea560..c34b182aa6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
@@ -36,6 +36,7 @@ import net.mullvad.mullvadvpn.relaylist.newFilterOnSearch
import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.RelayListRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.usecase.AvailableProvidersUseCase
import net.mullvad.mullvadvpn.usecase.FilteredRelayListUseCase
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
@@ -51,6 +52,7 @@ class SelectLocationViewModel(
private val customListActionUseCase: CustomListActionUseCase,
private val filteredRelayListUseCase: FilteredRelayListUseCase,
private val relayListRepository: RelayListRepository,
+ private val settingsRepository: SettingsRepository,
) : ViewModel() {
private val _searchTerm = MutableStateFlow(EMPTY_SEARCH_TERM)
@@ -110,7 +112,8 @@ class SelectLocationViewModel(
relayListFilterRepository.selectedOwnership,
relayListFilterRepository.selectedProviders,
availableProvidersUseCase(),
- ) { selectedOwnership, selectedConstraintProviders, allProviders ->
+ settingsRepository.settingsUpdates,
+ ) { selectedOwnership, selectedConstraintProviders, allProviders, settings ->
val ownershipFilter = selectedOwnership.getOrNull()
val providerCountFilter =
when (selectedConstraintProviders) {
@@ -122,7 +125,6 @@ class SelectLocationViewModel(
)
.size
}
-
buildList {
if (ownershipFilter != null) {
add(FilterChip.Ownership(ownershipFilter))
@@ -130,6 +132,9 @@ class SelectLocationViewModel(
if (providerCountFilter != null) {
add(FilterChip.Provider(providerCountFilter))
}
+ if (settings?.isDaitaEnabled() == true) {
+ add(FilterChip.Daita)
+ }
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
index abaad265fe..af2ea72e4e 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
@@ -63,6 +63,7 @@ class VpnSettingsViewModel(
mtuValue = settings?.tunnelOptions?.wireguard?.mtu,
isAutoConnectEnabled = settings?.autoConnect ?: false,
isLocalNetworkSharingEnabled = settings?.allowLan ?: false,
+ isDaitaEnabled = settings?.isDaitaEnabled() ?: false,
isCustomDnsEnabled = settings?.isCustomDnsEnabled() ?: false,
customDnsList = settings?.addresses()?.asStringAddressList() ?: listOf(),
contentBlockersOptions =
@@ -123,6 +124,14 @@ class VpnSettingsViewModel(
}
}
+ fun onToggleDaita(enable: Boolean) {
+ viewModelScope.launch(dispatcher) {
+ repository.setDaitaEnabled(enable).onLeft {
+ _uiSideEffect.send(VpnSettingsSideEffect.ShowToast.GenericError)
+ }
+ }
+ }
+
fun onDnsDialogDismissed() {
if (vmState.value.customDnsList.isEmpty()) {
onToggleCustomDns(enable = false)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
index 534263b44b..676a10cd70 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
@@ -13,6 +13,7 @@ data class VpnSettingsViewModelState(
val mtuValue: Mtu?,
val isAutoConnectEnabled: Boolean,
val isLocalNetworkSharingEnabled: Boolean,
+ val isDaitaEnabled: Boolean,
val isCustomDnsEnabled: Boolean,
val customDnsList: List<CustomDnsItem>,
val contentBlockersOptions: DefaultDnsOptions,
@@ -29,6 +30,7 @@ data class VpnSettingsViewModelState(
mtuValue,
isAutoConnectEnabled,
isLocalNetworkSharingEnabled,
+ isDaitaEnabled,
isCustomDnsEnabled,
customDnsList,
contentBlockersOptions,
@@ -47,6 +49,7 @@ data class VpnSettingsViewModelState(
mtuValue = null,
isAutoConnectEnabled = false,
isLocalNetworkSharingEnabled = false,
+ isDaitaEnabled = false,
isCustomDnsEnabled = false,
customDnsList = listOf(),
contentBlockersOptions = DefaultDnsOptions(),
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 e4012abd9e..1e0de9c96e 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
@@ -322,6 +322,7 @@ class CustomListLocationsViewModelTest {
ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
),
)
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
index 5a44f6db06..bee888d279 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
@@ -32,10 +32,12 @@ import net.mullvad.mullvadvpn.lib.model.Provider
import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.lib.model.RelayItemId
+import net.mullvad.mullvadvpn.lib.model.Settings
import net.mullvad.mullvadvpn.relaylist.descendants
import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.RelayListRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.usecase.AvailableProvidersUseCase
import net.mullvad.mullvadvpn.usecase.FilteredRelayListUseCase
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
@@ -58,6 +60,9 @@ class SelectLocationViewModelTest {
private val mockCustomListsRepository: CustomListsRepository = mockk()
private val mockCustomListsRelayItemUseCase: CustomListsRelayItemUseCase = mockk()
+ private val mockSettingsRepository: SettingsRepository = mockk()
+ private val settingsFlow = MutableStateFlow(mockk<Settings>(relaxed = true))
+
private lateinit var viewModel: SelectLocationViewModel
private val allProviders = MutableStateFlow<List<Provider>>(emptyList())
@@ -79,6 +84,7 @@ class SelectLocationViewModelTest {
every { mockFilteredRelayListUseCase() } returns filteredRelayList
every { mockFilteredCustomListRelayItemsUseCase() } returns filteredCustomRelayListItems
every { mockCustomListsRelayItemUseCase() } returns customListsRelayItem
+ every { mockSettingsRepository.settingsUpdates } returns settingsFlow
mockkStatic(RELAY_LIST_EXTENSIONS)
mockkStatic(RELAY_ITEM_EXTENSIONS)
@@ -93,6 +99,7 @@ class SelectLocationViewModelTest {
relayListRepository = mockRelayListRepository,
customListsRepository = mockCustomListsRepository,
customListsRelayItemUseCase = mockCustomListsRelayItemUseCase,
+ settingsRepository = mockSettingsRepository,
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
index c2f9ca34a6..89456c1d02 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
@@ -113,7 +113,11 @@ class VpnSettingsViewModelTest {
val mockTunnelOptions: TunnelOptions = mockk(relaxed = true)
// Can not use a mock here since mocking a value class val leads to class cast exception
val mockWireguardTunnelOptions =
- WireguardTunnelOptions(mtu = Mtu(0), quantumResistant = expectedResistantState)
+ WireguardTunnelOptions(
+ mtu = Mtu(0),
+ quantumResistant = expectedResistantState,
+ daita = false,
+ )
every { mockSettings.tunnelOptions } returns mockTunnelOptions
every { mockTunnelOptions.wireguard } returns mockWireguardTunnelOptions
@@ -146,6 +150,7 @@ class VpnSettingsViewModelTest {
WireguardTunnelOptions(
mtu = null,
quantumResistant = QuantumResistantState.Off,
+ daita = false,
),
dnsOptions = mockk(relaxed = true),
)
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
index b51fd7eb2f..f444edce39 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
@@ -104,6 +104,7 @@ import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.lib.model.SetAllowLanError
import net.mullvad.mullvadvpn.lib.model.SetApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.SetAutoConnectError
+import net.mullvad.mullvadvpn.lib.model.SetDaitaSettingsError
import net.mullvad.mullvadvpn.lib.model.SetDnsOptionsError
import net.mullvad.mullvadvpn.lib.model.SetObfuscationOptionsError
import net.mullvad.mullvadvpn.lib.model.SetRelayLocationError
@@ -501,6 +502,15 @@ class ManagementService(
.mapLeft(SetAllowLanError::Unknown)
.mapEmpty()
+ suspend fun setDaitaEnabled(enabled: Boolean): Either<SetDaitaSettingsError, Unit> =
+ Either.catch {
+ val daitaSettings =
+ ManagementInterface.DaitaSettings.newBuilder().setEnabled(enabled).build()
+ grpc.setDaitaSettings(daitaSettings)
+ }
+ .mapLeft(SetDaitaSettingsError::Unknown)
+ .mapEmpty()
+
suspend fun setRelayLocation(location: ModelRelayItemId): Either<SetRelayLocationError, Unit> =
Either.catch {
val currentRelaySettings = getSettings().relaySettings
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index 5e8df1edc4..1ff297312b 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -68,6 +68,7 @@ import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.Udp2TcpObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData
+import net.mullvad.mullvadvpn.lib.model.WireguardRelayEndpointData
import net.mullvad.mullvadvpn.lib.model.WireguardTunnelOptions
import org.joda.time.Instant
@@ -158,6 +159,7 @@ internal fun ManagementInterface.TunnelEndpoint.toDomain(): TunnelEndpoint =
} else {
null
},
+ daita = daita,
)
internal fun ManagementInterface.ObfuscationEndpoint.toDomain(): ObfuscationEndpoint =
@@ -370,6 +372,7 @@ internal fun ManagementInterface.TunnelOptions.WireguardOptions.toDomain(): Wire
WireguardTunnelOptions(
mtu = if (hasMtu()) Mtu(mtu) else null,
quantumResistant = quantumResistant.toDomain(),
+ daita = daita.enabled,
)
internal fun ManagementInterface.QuantumResistantState.toDomain(): QuantumResistantState =
@@ -442,6 +445,9 @@ internal fun ManagementInterface.RelayList.toDomain(): RelayList =
internal fun ManagementInterface.WireguardEndpointData.toDomain(): WireguardEndpointData =
WireguardEndpointData(portRangesList.map { it.toDomain() })
+internal fun ManagementInterface.WireguardRelayEndpointData.toDomain(): WireguardRelayEndpointData =
+ WireguardRelayEndpointData(daita)
+
internal fun ManagementInterface.PortRange.toDomain(): PortRange = PortRange(first..last)
/**
@@ -493,6 +499,12 @@ internal fun ManagementInterface.Relay.toDomain(
ProviderId(provider),
ownership = if (owned) Ownership.MullvadOwned else Ownership.Rented,
),
+ daita =
+ if (
+ hasEndpointData() && endpointType == ManagementInterface.Relay.RelayType.WIREGUARD
+ ) {
+ ManagementInterface.WireguardRelayEndpointData.parseFrom(endpointData.value).daita
+ } else false,
)
internal fun ManagementInterface.Device.toDomain(): Device =
@@ -596,11 +608,11 @@ internal fun ManagementInterface.FeatureIndicator.toDomain() =
ManagementInterface.FeatureIndicator.SERVER_IP_OVERRIDE ->
FeatureIndicator.SERVER_IP_OVERRIDE
ManagementInterface.FeatureIndicator.CUSTOM_MTU -> FeatureIndicator.CUSTOM_MTU
+ ManagementInterface.FeatureIndicator.DAITA -> FeatureIndicator.DAITA
ManagementInterface.FeatureIndicator.LOCKDOWN_MODE,
ManagementInterface.FeatureIndicator.SHADOWSOCKS,
ManagementInterface.FeatureIndicator.MULTIHOP,
ManagementInterface.FeatureIndicator.BRIDGE_MODE,
ManagementInterface.FeatureIndicator.CUSTOM_MSS_FIX,
- ManagementInterface.FeatureIndicator.DAITA,
ManagementInterface.FeatureIndicator.UNRECOGNIZED -> error("Feature not supported")
}
diff --git a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
index 1494851bcb..e496c6d5aa 100644
--- a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
+++ b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
@@ -29,6 +29,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay10 =
RelayItem.Location.Relay(
@@ -39,6 +40,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relay9 assertOrderBothDirection relay10
@@ -55,6 +57,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -65,6 +68,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -82,6 +86,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay1 =
RelayItem.Location.Relay(
@@ -92,6 +97,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay3 =
RelayItem.Location.Relay(
@@ -102,6 +108,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay100 =
RelayItem.Location.Relay(
@@ -112,6 +119,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relay001 assertOrderBothDirection relay1
@@ -131,6 +139,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -141,6 +150,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -158,6 +168,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay005 =
RelayItem.Location.Relay(
@@ -168,6 +179,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relay001 assertOrderBothDirection relay005
@@ -184,6 +196,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relayAr8 =
RelayItem.Location.Relay(
@@ -194,6 +207,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relaySe5 =
RelayItem.Location.Relay(
@@ -204,6 +218,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relaySe10 =
RelayItem.Location.Relay(
@@ -214,6 +229,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relayAr2 assertOrderBothDirection relayAr8
@@ -232,6 +248,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay2w =
RelayItem.Location.Relay(
@@ -242,6 +259,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relay2c assertOrderBothDirection relay2w
@@ -258,6 +276,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
val relay22b =
RelayItem.Location.Relay(
@@ -268,6 +287,7 @@ class RelayNameComparatorTest {
providerId = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
),
+ daita = false,
)
relay22a assertOrderBothDirection relay22b
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
index 7ad0b3ab69..d11f405869 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
@@ -9,12 +9,12 @@ enum class FeatureIndicator {
CUSTOM_DNS,
SERVER_IP_OVERRIDE,
CUSTOM_MTU,
+ DAITA,
// Currently not supported
// LOCKDOWN_MODE,
// SHADOWSOCKS,
// MULTIHOP,
// BRIDGE_MODE,
// CUSTOM_MSS_FIX,
- // DAITA,
// UNRECOGNIZED,
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
index edf00d4cb4..af96c4d94d 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
@@ -58,6 +58,7 @@ sealed interface RelayItem {
override val id: GeoLocationId.Hostname,
val provider: Provider,
override val active: Boolean,
+ val daita: Boolean,
) : Location {
override val name: String = id.code
override val hasChildren: Boolean = false
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDaitaSettingsError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDaitaSettingsError.kt
new file mode 100644
index 0000000000..f636267c09
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDaitaSettingsError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetDaitaSettingsError {
+ data class Unknown(val throwable: Throwable) : SetDaitaSettingsError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
index 0c15202bee..f59d85184c 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
@@ -15,5 +15,7 @@ data class Settings(
val splitTunnelSettings: SplitTunnelSettings,
val apiAccessMethodSettings: List<ApiAccessMethodSetting>,
) {
+ fun isDaitaEnabled() = tunnelOptions.wireguard.daita
+
companion object
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt
index 1465997cc5..3902e5c965 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt
@@ -4,4 +4,5 @@ data class TunnelEndpoint(
val endpoint: Endpoint,
val quantumResistant: Boolean,
val obfuscation: ObfuscationEndpoint?,
+ val daita: Boolean,
)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
index 53d468c01e..1849c5abf9 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
@@ -38,4 +38,12 @@ sealed class TunnelState {
is Error -> this.errorState.isBlocking
}
}
+
+ fun isUsingDaita(): Boolean {
+ return when (this) {
+ is Connected -> endpoint.daita
+ is Connecting -> endpoint?.daita ?: false
+ else -> false
+ }
+ }
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardRelayEndpointData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardRelayEndpointData.kt
new file mode 100644
index 0000000000..9e328b92e6
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardRelayEndpointData.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class WireguardRelayEndpointData(val daita: Boolean)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt
index 573f08213e..70b1599c55 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt
@@ -1,3 +1,7 @@
package net.mullvad.mullvadvpn.lib.model
-data class WireguardTunnelOptions(val mtu: Mtu?, val quantumResistant: QuantumResistantState)
+data class WireguardTunnelOptions(
+ val mtu: Mtu?,
+ val quantumResistant: QuantumResistantState,
+ val daita: Boolean,
+)
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index 071f45ae23..b72902897f 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -385,4 +385,10 @@
<string name="failed_to_set_current_unknown_error">Failed to set to current - Unknown reason</string>
<string name="location_was_removed_from_list">%s was removed from \"%s\"</string>
<string name="create_custom_list_message">\"%s\" was created</string>
+ <string name="daita_info">%s (%s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.</string>
+ <string name="daita_warning">Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %s.</string>
+ <string name="connected_using_daita">%s using %s</string>
+ <string name="setting_chip">Setting: %s</string>
+ <string name="enable_anyway">Enable anyway</string>
+ <string name="daita_relay_subset_warning">This feature isn’t available on all servers. You might need to change location after enabling.</string>
</resources>
diff --git a/android/lib/resource/src/main/res/values/strings_non_translatable.xml b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
index 110e112e99..9cf571171a 100644
--- a/android/lib/resource/src/main/res/values/strings_non_translatable.xml
+++ b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
@@ -14,4 +14,6 @@
<string name="local_network_sharing_ip_ranges">
<![CDATA[<ul><li>10.0.0.0/8</li><li>172.16.0.0/12</li><li>192.168.0.0/16</li><li>169.254.0.0/16</li><li>fe80::/10</li><li>fc00::/7</li></ul>]]>
</string>
+ <string name="daita">DAITA</string>
+ <string name="daita_full">Defence against AI-guided Traffic Analysis</string>
</resources>
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt
deleted file mode 100644
index 71a05e6743..0000000000
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.mullvad.mullvadvpn.service
-
-import android.content.Context
-import java.io.File
-import java.io.FileOutputStream
-
-class FileResourceExtractor(val context: Context) {
- fun extract(asset: String, force: Boolean = false) {
- val destination = File(context.filesDir, asset)
-
- if (!destination.exists() || force) {
- extractFile(asset, destination)
- }
- }
-
- private fun extractFile(asset: String, destination: File) {
- val destinationStream = FileOutputStream(destination)
-
- context.assets.open(asset).copyTo(destinationStream)
-
- destinationStream.close()
- }
-}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index 2db5a00fdb..ebdcbec780 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -10,7 +10,6 @@ import androidx.core.content.getSystemService
import androidx.lifecycle.lifecycleScope
import arrow.atomic.AtomicInt
import co.touchlab.kermit.Logger
-import java.io.File
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
@@ -27,11 +26,13 @@ import net.mullvad.mullvadvpn.service.migration.MigrateSplitTunneling
import net.mullvad.mullvadvpn.service.notifications.ForegroundNotificationManager
import net.mullvad.mullvadvpn.service.notifications.NotificationChannelFactory
import net.mullvad.mullvadvpn.service.notifications.NotificationManager
+import net.mullvad.mullvadvpn.service.util.extractAndOverwriteIfAssetMoreRecent
import net.mullvad.talpid.TalpidVpnService
import org.koin.android.ext.android.getKoin
import org.koin.core.context.loadKoinModules
-private const val RELAYS_FILE = "relays.json"
+private const val RELAY_LIST_ASSET_NAME = "relays.json"
+private const val MAYBENOT_MACHINES_ASSET_NAME = "maybenot_machines"
class MullvadVpnService : TalpidVpnService() {
@@ -72,7 +73,7 @@ class MullvadVpnService : TalpidVpnService() {
keyguardManager = getSystemService<KeyguardManager>()!!
- prepareFiles(this@MullvadVpnService)
+ prepareFiles()
migrateSplitTunneling.migrate()
// If it is a debug build and we have an api override in the intent, use it
@@ -221,16 +222,11 @@ class MullvadVpnService : TalpidVpnService() {
return this?.action == SERVICE_INTERFACE
}
- private fun prepareFiles(context: Context) {
- val shouldOverwriteRelayList =
- lastUpdatedTime(context) > File(context.filesDir, RELAYS_FILE).lastModified()
-
- FileResourceExtractor(context).apply { extract(RELAYS_FILE, shouldOverwriteRelayList) }
+ private fun Context.prepareFiles() {
+ extractAndOverwriteIfAssetMoreRecent(RELAY_LIST_ASSET_NAME)
+ extractAndOverwriteIfAssetMoreRecent(MAYBENOT_MACHINES_ASSET_NAME)
}
- private fun lastUpdatedTime(context: Context): Long =
- context.packageManager.getPackageInfo(context.packageName, 0).lastUpdateTime
-
companion object {
init {
System.loadLibrary("mullvad_jni")
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ContextExtensions.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ContextExtensions.kt
new file mode 100644
index 0000000000..51240fa16d
--- /dev/null
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ContextExtensions.kt
@@ -0,0 +1,23 @@
+package net.mullvad.mullvadvpn.service.util
+
+import android.content.Context
+import java.io.File
+import java.io.FileOutputStream
+
+fun Context.extractAndOverwriteIfAssetMoreRecent(assetName: String) {
+ val forceOverwriteIfMoreRecent = lastUpdatedTime() > File(filesDir, assetName).lastModified()
+ val destination = File(filesDir, assetName)
+
+ if (!destination.exists() || forceOverwriteIfMoreRecent) {
+ extractFile(assetName, destination)
+ }
+}
+
+private fun Context.lastUpdatedTime(): Long =
+ packageManager.getPackageInfo(packageName, 0).lastUpdateTime
+
+private fun Context.extractFile(asset: String, destination: File) {
+ val destinationStream = FileOutputStream(destination)
+ assets.open(asset).copyTo(destinationStream)
+ destinationStream.close()
+}