summaryrefslogtreecommitdiffhomepage
path: root/android/lib
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
commitad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch)
tree9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/lib
parent8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff)
parent767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff)
downloadmullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.tar.xz
mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.zip
Merge branch 'android-grpc'
Diffstat (limited to 'android/lib')
-rw-r--r--android/lib/billing/build.gradle.kts9
-rw-r--r--android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepository.kt58
-rw-r--r--android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/PlayPurchaseRepository.kt34
-rw-r--r--android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt26
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt (renamed from android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt)7
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt6
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt3
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt46
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt15
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt45
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt10
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt87
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt91
-rw-r--r--android/lib/daemon-grpc/build.gradle.kts85
-rw-r--r--android/lib/daemon-grpc/src/main/AndroidManifest.xml (renamed from android/lib/ipc/src/main/AndroidManifest.xml)0
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt565
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparator.kt33
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt140
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt540
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/LogInterceptor.kt20
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/ManagedChannel.kt24
-rw-r--r--android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt282
-rw-r--r--android/lib/intent-provider/build.gradle.kts (renamed from android/lib/ipc/build.gradle.kts)16
-rw-r--r--android/lib/intent-provider/src/main/AndroidManifest.xml1
-rw-r--r--android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt16
-rw-r--r--android/lib/ipc/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlowTest.kt44
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt53
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt86
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt38
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt31
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt7
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageHandler.kt14
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt134
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/extensions/MessengerExtensions.kt40
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt6
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt2
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/CameraPosition.kt2
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/Marker.kt2
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt2
-rw-r--r--android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes/LocationMarker.kt2
-rw-r--r--android/lib/model/build.gradle.kts8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountId.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountToken.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ActionAfterDisconnect.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddSplitTunnelingAppError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppId.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppVersionInfo.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/BuildVersion.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ClearAllOverridesError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Constraint.kt21
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateAccountError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateCustomListError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomDnsOptions.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomList.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomListName.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DefaultDnsOptions.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DefaultDnsOptions.kt)11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteCustomListError.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteDeviceError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceId.kt13
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt21
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsOptions.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsOptions.kt)11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsState.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsState.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/EnableSplitTunnelingError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Endpoint.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorState.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt34
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GeoIpLocation.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt)7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountDataError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountHistoryError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceListError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceStateError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt)4
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Latitude.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ListDevicesError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LoginAccountError.kt15
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Longitude.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Mtu.kt28
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt25
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt20
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannel.kt15
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannelId.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationId.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt25
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationUpdate.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationEndpoint.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationSettings.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ObfuscationSettings.kt)11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Ownership.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchase.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseInitError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchasePaymentToken.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseVerifyError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PortRange.kt30
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Provider.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ProviderId.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Providers.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/QuantumResistantState.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherError.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayConstraints.kt13
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt88
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItemId.kt48
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayList.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayOverride.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt)11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelaySettings.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveDeviceError.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveSplitTunnelingAppError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SelectedObfuscation.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAllowLanError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAutoConnectError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDnsOptionsError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetObfuscationOptionsError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetRelayLocationError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardConstraintsError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardMtuError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardQuantumResistantError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt)16
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SettingsPatchError.kt (renamed from android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt)8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SplitTunnelSettings.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TransportProtocol.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt)2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt)8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelOptions.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt35
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Udp2TcpObfuscationSettings.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateCustomListError.kt35
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WebsiteAuthToken.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardConstraints.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardEndpointData.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/extensions/String.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountAndDevice.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountCreationResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt15
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountHistory.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountToken.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AppVersionInfo.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Constraint.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CreateCustomListResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomDnsOptions.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomList.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsError.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsSettings.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomTunnelEndpoint.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Device.kt40
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEvent.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEventCause.kt13
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceList.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceListEvent.kt15
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DevicePort.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt28
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt28
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GetAccountDataResult.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginResult.kt13
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Ownership.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchase.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitError.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyError.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Port.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PortRange.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Providers.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PublicKey.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/QuantumResistantState.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt16
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayConstraints.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt14
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayList.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCity.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCountry.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelaySettings.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceEvent.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceResult.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SelectedObfuscation.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ServiceResult.kt23
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelOptions.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt46
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Udp2TcpObfuscationSettings.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/UpdateCustomListResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmission.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionError.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionResult.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardConstraints.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardTunnelOptions.kt8
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatLongTest.kt (renamed from android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt)2
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatitudeTest.kt (renamed from android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt)2
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LongitudeTest.kt (renamed from android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt)2
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml16
-rw-r--r--android/lib/shared/build.gradle.kts47
-rw-r--r--android/lib/shared/src/main/AndroidManifest.xml1
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt84
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt26
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/DeviceRepository.kt36
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VoucherRepository.kt13
-rw-r--r--android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt11
-rw-r--r--android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt54
-rw-r--r--android/lib/talpid/build.gradle.kts3
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt8
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/LifecycleVpnService.kt56
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt30
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt)6
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/InetNetwork.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt)2
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/TunConfig.kt (renamed from android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt)3
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt8
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt8
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt9
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt11
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt6
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt36
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/FirewallPolicyError.kt9
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt76
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt9
221 files changed, 3031 insertions, 1735 deletions
diff --git a/android/lib/billing/build.gradle.kts b/android/lib/billing/build.gradle.kts
index 26cc345556..6bb4e5e7a6 100644
--- a/android/lib/billing/build.gradle.kts
+++ b/android/lib/billing/build.gradle.kts
@@ -49,12 +49,15 @@ dependencies {
//Model
implementation(project(Dependencies.Mullvad.modelLib))
- //IPC
- implementation(project(Dependencies.Mullvad.ipcLib))
-
//Payment library
implementation(project(Dependencies.Mullvad.paymentLib))
+ //Either
+ implementation(Dependencies.Arrow.core)
+
+ // Management service
+ implementation(project(Dependencies.Mullvad.daemonGrpc))
+
// Test dependencies
testRuntimeOnly(Dependencies.junitEngine)
diff --git a/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepository.kt b/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepository.kt
index 76df623ada..8b3ad66171 100644
--- a/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepository.kt
+++ b/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepository.kt
@@ -15,15 +15,14 @@ import net.mullvad.mullvadvpn.lib.billing.extension.toPaymentStatus
import net.mullvad.mullvadvpn.lib.billing.extension.toPurchaseResult
import net.mullvad.mullvadvpn.lib.billing.model.BillingException
import net.mullvad.mullvadvpn.lib.billing.model.PurchaseEvent
+import net.mullvad.mullvadvpn.lib.model.PlayPurchase
+import net.mullvad.mullvadvpn.lib.model.PlayPurchasePaymentToken
import net.mullvad.mullvadvpn.lib.payment.PaymentRepository
import net.mullvad.mullvadvpn.lib.payment.ProductIds
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.ProductId
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
import net.mullvad.mullvadvpn.lib.payment.model.VerificationResult
-import net.mullvad.mullvadvpn.model.PlayPurchase
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
class BillingPaymentRepository(
private val billingRepository: BillingRepository,
@@ -74,19 +73,20 @@ class BillingPaymentRepository(
// Get transaction id
emit(PurchaseResult.FetchingObfuscationId)
- val obfuscatedId: String =
- when (val result = initialisePurchase()) {
- is PlayPurchaseInitResult.Ok -> result.obfuscatedId
- else -> {
- emit(PurchaseResult.Error.TransactionIdError(productId, null))
- return@flow
- }
- }
+ val obfuscatedId: PlayPurchasePaymentToken =
+ initialisePurchase()
+ .fold(
+ {
+ emit(PurchaseResult.Error.TransactionIdError(productId, null))
+ return@flow
+ },
+ { it }
+ )
val result =
billingRepository.startPurchaseFlow(
productDetails = productDetails,
- obfuscatedId = obfuscatedId,
+ obfuscatedId = obfuscatedId.value,
activityProvider = activityProvider
)
@@ -115,11 +115,13 @@ class BillingPaymentRepository(
emit(PurchaseResult.Completed.Pending)
} else {
emit(PurchaseResult.VerificationStarted)
- if (verifyPurchase(event.purchases.first()) == PlayPurchaseVerifyResult.Ok) {
- emit(PurchaseResult.Completed.Success)
- } else {
- emit(PurchaseResult.Error.VerificationError(null))
- }
+ emit(
+ verifyPurchase(event.purchases.first())
+ .fold(
+ { PurchaseResult.Error.VerificationError(null) },
+ { PurchaseResult.Completed.Success }
+ )
+ )
}
}
PurchaseEvent.UserCanceled -> emit(event.toPurchaseResult())
@@ -135,13 +137,12 @@ class BillingPaymentRepository(
val purchases = purchasesResult.nonPendingPurchases()
if (purchases.isNotEmpty()) {
emit(VerificationResult.VerificationStarted)
- val verificationResult = verifyPurchase(purchases.first())
emit(
- when (verificationResult) {
- is PlayPurchaseVerifyResult.Error ->
- VerificationResult.Error.VerificationError(null)
- PlayPurchaseVerifyResult.Ok -> VerificationResult.Success
- }
+ verifyPurchase(purchases.first())
+ .fold(
+ { VerificationResult.Error.VerificationError(null) },
+ { VerificationResult.Success }
+ )
)
} else {
emit(VerificationResult.NothingToVerify)
@@ -152,16 +153,13 @@ class BillingPaymentRepository(
}
}
- private suspend fun initialisePurchase(): PlayPurchaseInitResult {
- return playPurchaseRepository.initializePlayPurchase()
- }
+ private suspend fun initialisePurchase() = playPurchaseRepository.initializePlayPurchase()
- private suspend fun verifyPurchase(purchase: Purchase): PlayPurchaseVerifyResult {
- return playPurchaseRepository.verifyPlayPurchase(
+ private suspend fun verifyPurchase(purchase: Purchase) =
+ playPurchaseRepository.verifyPlayPurchase(
PlayPurchase(
productId = purchase.products.first(),
- purchaseToken = purchase.purchaseToken,
+ purchaseToken = PlayPurchasePaymentToken(purchase.purchaseToken),
)
)
- }
}
diff --git a/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/PlayPurchaseRepository.kt b/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/PlayPurchaseRepository.kt
index ac71372f76..8e89cb8f95 100644
--- a/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/PlayPurchaseRepository.kt
+++ b/android/lib/billing/src/main/kotlin/net/mullvad/mullvadvpn/lib/billing/PlayPurchaseRepository.kt
@@ -1,33 +1,11 @@
package net.mullvad.mullvadvpn.lib.billing
-import kotlinx.coroutines.flow.first
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.MessageHandler
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.lib.ipc.events
-import net.mullvad.mullvadvpn.model.PlayPurchase
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitError
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyError
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.PlayPurchase
-class PlayPurchaseRepository(private val messageHandler: MessageHandler) {
- suspend fun initializePlayPurchase(): PlayPurchaseInitResult {
- val result = messageHandler.trySendRequest(Request.InitPlayPurchase)
+class PlayPurchaseRepository(private val managementService: ManagementService) {
+ suspend fun initializePlayPurchase() = managementService.initializePlayPurchase()
- return if (result) {
- messageHandler.events<Event.PlayPurchaseInitResultEvent>().first().result
- } else {
- PlayPurchaseInitResult.Error(PlayPurchaseInitError.OtherError)
- }
- }
-
- suspend fun verifyPlayPurchase(purchase: PlayPurchase): PlayPurchaseVerifyResult {
- val result = messageHandler.trySendRequest(Request.VerifyPlayPurchase(purchase))
- return if (result) {
- messageHandler.events<Event.PlayPurchaseVerifyResultEvent>().first().result
- } else {
- PlayPurchaseVerifyResult.Error(PlayPurchaseVerifyError.OtherError)
- }
- }
+ suspend fun verifyPlayPurchase(purchase: PlayPurchase) =
+ managementService.verifyPlayPurchase(purchase)
}
diff --git a/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt b/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt
index c4d1b04905..ad716cd30c 100644
--- a/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt
+++ b/android/lib/billing/src/test/kotlin/net/mullvad/mullvadvpn/lib/billing/BillingPaymentRepositoryTest.kt
@@ -1,6 +1,8 @@
package net.mullvad.mullvadvpn.lib.billing
import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
@@ -17,14 +19,13 @@ import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.billing.extension.toPaymentProduct
import net.mullvad.mullvadvpn.lib.billing.model.PurchaseEvent
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.lib.model.PlayPurchaseInitError
+import net.mullvad.mullvadvpn.lib.model.PlayPurchasePaymentToken
+import net.mullvad.mullvadvpn.lib.model.PlayPurchaseVerifyError
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
import net.mullvad.mullvadvpn.lib.payment.model.ProductId
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitError
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyError
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@@ -170,7 +171,7 @@ class BillingPaymentRepositoryTest {
coEvery { mockBillingRepository.queryProducts(listOf(mockProductId.value)) } returns
mockProductDetailsResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Error(PlayPurchaseInitError.OtherError)
+ PlayPurchaseInitError.OtherError.left()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
@@ -206,7 +207,7 @@ class BillingPaymentRepositoryTest {
)
} returns mockBillingResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Ok("MOCK")
+ PlayPurchasePaymentToken("MOCK").right()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
@@ -241,7 +242,7 @@ class BillingPaymentRepositoryTest {
)
} returns mockBillingResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Ok(mockObfuscatedId)
+ PlayPurchasePaymentToken(mockObfuscatedId).right()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
@@ -283,9 +284,9 @@ class BillingPaymentRepositoryTest {
)
} returns mockBillingResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Ok("MOCK-ID")
+ PlayPurchasePaymentToken("MOCK-ID").right()
coEvery { mockPlayPurchaseRepository.verifyPlayPurchase(any()) } returns
- PlayPurchaseVerifyResult.Error(PlayPurchaseVerifyError.OtherError)
+ PlayPurchaseVerifyError.OtherError.left()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
@@ -326,9 +327,8 @@ class BillingPaymentRepositoryTest {
)
} returns mockBillingResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Ok("MOCK")
- coEvery { mockPlayPurchaseRepository.verifyPlayPurchase(any()) } returns
- PlayPurchaseVerifyResult.Ok
+ PlayPurchasePaymentToken("MOCK").right()
+ coEvery { mockPlayPurchaseRepository.verifyPlayPurchase(any()) } returns Unit.right()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
@@ -368,7 +368,7 @@ class BillingPaymentRepositoryTest {
)
} returns mockBillingResult
coEvery { mockPlayPurchaseRepository.initializePlayPurchase() } returns
- PlayPurchaseInitResult.Ok("MOCK")
+ PlayPurchasePaymentToken("MOCK").right()
// Act, Assert
paymentRepository.purchaseProduct(mockProductId, mockk()).test {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt
index 09210ffa03..1636bbd46f 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt
@@ -2,13 +2,8 @@ package net.mullvad.mullvadvpn.lib.common.constant
// Do not use in cases where the application id is expected since the application id will differ
// between different builds.
-private const val MULLVAD_PACKAGE_NAME = "net.mullvad.mullvadvpn"
+internal const val MULLVAD_PACKAGE_NAME = "net.mullvad.mullvadvpn"
// Classes
const val MAIN_ACTIVITY_CLASS = "$MULLVAD_PACKAGE_NAME.ui.MainActivity"
const val VPN_SERVICE_CLASS = "$MULLVAD_PACKAGE_NAME.service.MullvadVpnService"
-
-// Actions
-const val KEY_CONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.connect_action"
-const val KEY_DISCONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.disconnect_action"
-const val KEY_QUIT_ACTION = "$MULLVAD_PACKAGE_NAME.quit_action"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
new file mode 100644
index 0000000000..ea420f2d0a
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.common.constant
+
+// Actions
+const val KEY_CONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.connect_action"
+const val KEY_DISCONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.disconnect_action"
+const val KEY_REQUEST_VPN_PERMISSION = "$MULLVAD_PACKAGE_NAME.request_vpn_permission"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt
new file mode 100644
index 0000000000..d2ae3f1871
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.common.constant
+
+const val TAG = "mullvad"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
index 42f0663967..bf94c80778 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
@@ -1,47 +1,9 @@
package net.mullvad.mullvadvpn.lib.common.util
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.IBinder
-import android.util.Log
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import net.mullvad.mullvadvpn.model.ServiceResult
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.withTimeoutOrNull
-fun <T> SendChannel<T>.safeOffer(element: T): Boolean {
- return runCatching { trySend(element).isSuccess }.getOrDefault(false)
-}
-
-fun Context.bindServiceFlow(intent: Intent, flags: Int = 0): Flow<ServiceResult> = callbackFlow {
- val connectionCallback =
- object : ServiceConnection {
- override fun onServiceConnected(className: ComponentName, binder: IBinder) {
- safeOffer(ServiceResult(binder))
- }
-
- override fun onServiceDisconnected(className: ComponentName) {
- safeOffer(ServiceResult.NOT_CONNECTED)
- bindService(intent, this, flags)
- }
- }
-
- bindService(intent, connectionCallback, flags)
-
- awaitClose {
- safeOffer(ServiceResult.NOT_CONNECTED)
-
- Dispatchers.Default.dispatch(EmptyCoroutineContext) {
- try {
- unbindService(connectionCallback)
- } catch (e: IllegalArgumentException) {
- Log.e("mullvad", "Cannot unbind as no binding exists.")
- }
- }
- }
+suspend fun <T> Flow<T>.firstOrNullWithTimeout(timeMillis: Long): T? {
+ return withTimeoutOrNull(timeMillis) { firstOrNull() }
}
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
index 8ef70dad92..d714dae327 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
@@ -4,15 +4,20 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
-import net.mullvad.mullvadvpn.lib.common.R
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
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 createAccountUri(accountUri: String, websiteAuthToken: WebsiteAuthToken?): Uri {
+ val urlString = buildString {
+ append(accountUri)
+ if (websiteAuthToken != null) {
+ append("?token=")
+ append(websiteAuthToken.value)
+ }
+ }
+ return Uri.parse(urlString)
}
fun Context.getAlwaysOnVpnAppName(): String? {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt
deleted file mode 100644
index 7fc37a752c..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-import java.util.concurrent.ConcurrentHashMap
-import kotlin.reflect.KClass
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedSendChannelException
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.flow.consumeAsFlow
-
-class DispatchingFlow<T : Any>(private val upstream: Flow<T>) : Flow<T> {
- private val subscribers = ConcurrentHashMap<KClass<out T>, SendChannel<T>>()
-
- fun <V : T> subscribe(variant: KClass<V>, capacity: Int = Channel.CONFLATED): Flow<V> {
- val channel = Channel<V>(capacity)
-
- // This is safe because `collect` will only send to this channel if the instance class is V
- @Suppress("UNCHECKED_CAST")
- subscribers[variant] = channel as SendChannel<T>
-
- return channel.consumeAsFlow()
- }
-
- fun <V : T> unsubscribe(variant: KClass<V>) = subscribers.remove(variant)
-
- @InternalCoroutinesApi
- override suspend fun collect(collector: FlowCollector<T>) {
- upstream.collect { event ->
- try {
- subscribers[event::class]?.send(event)
- } catch (closedException: ClosedSendChannelException) {
- subscribers.remove(event::class)
- }
-
- collector.emit(event)
- }
-
- subscribers.clear()
- }
-}
-
-fun <T : Any> Flow<T>.dispatchTo(configureSubscribers: DispatchingFlow<T>.() -> Unit) =
- DispatchingFlow(this).also(configureSubscribers)
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
index f906ee8f6d..2c9554a842 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
@@ -2,9 +2,9 @@ package net.mullvad.mullvadvpn.lib.common.util
import android.content.Context
import net.mullvad.mullvadvpn.lib.common.R
-import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.tunnel.ParameterGenerationError
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
import net.mullvad.talpid.util.addressString
fun ErrorState.getErrorNotificationResources(context: Context): ErrorNotificationMessage {
@@ -48,8 +48,8 @@ fun ErrorStateCause.errorMessageId(): Int {
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.FirewallPolicyError -> R.string.set_firewall_policy_error
+ is ErrorStateCause.DnsError -> R.string.set_dns_error
is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
is ErrorStateCause.IsOffline -> R.string.is_offline
is ErrorStateCause.TunnelParameterError -> {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt
deleted file mode 100644
index 448d96778f..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.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/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt
deleted file mode 100644
index edb76ed4ae..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.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/lib/daemon-grpc/build.gradle.kts b/android/lib/daemon-grpc/build.gradle.kts
new file mode 100644
index 0000000000..ed33aa4d09
--- /dev/null
+++ b/android/lib/daemon-grpc/build.gradle.kts
@@ -0,0 +1,85 @@
+import com.google.protobuf.gradle.proto
+
+plugins {
+ id(Dependencies.Plugin.androidLibraryId)
+ id(Dependencies.Plugin.kotlinAndroidId)
+ id(Dependencies.Plugin.kotlinParcelizeId)
+ id(Dependencies.Plugin.protobufId) version Versions.Plugin.protobuf
+ id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5
+}
+
+android {
+ namespace = "net.mullvad.mullvadvpn.lib.daemon.grpc"
+ compileSdk = Versions.Android.compileSdkVersion
+
+ defaultConfig { minSdk = Versions.Android.minSdkVersion }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions { jvmTarget = Versions.jvmTarget }
+
+ lint {
+ lintConfig = file("${rootProject.projectDir}/config/lint.xml")
+ abortOnError = true
+ warningsAsErrors = true
+ }
+
+ sourceSets {
+ getByName("main") {
+ proto { srcDir("${rootProject.projectDir}/../mullvad-management-interface/proto") }
+ }
+ }
+}
+
+protobuf {
+ protoc { artifact = "com.google.protobuf:protoc:${Versions.Grpc.protobufVersion}" }
+ plugins {
+ create("java") { artifact = "io.grpc:protoc-gen-grpc-java:${Versions.Grpc.grpcVersion}" }
+ create("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${Versions.Grpc.grpcVersion}" }
+ create("grpckt") {
+ artifact = "io.grpc:protoc-gen-grpc-kotlin:${Versions.Grpc.grpcKotlinVersion}:jdk8@jar"
+ }
+ }
+ generateProtoTasks {
+ all().forEach {
+ it.plugins {
+ create("java") { option("lite") }
+ create("grpc") { option("lite") }
+ create("grpckt") { option("lite") }
+ }
+ it.builtins { create("kotlin") { option("lite") } }
+ }
+ }
+}
+
+dependencies {
+ implementation(project(Dependencies.Mullvad.commonLib))
+ implementation(project(Dependencies.Mullvad.modelLib))
+ implementation(project(Dependencies.Mullvad.talpidLib))
+
+ implementation(Dependencies.jodaTime)
+ implementation(Dependencies.Kotlin.stdlib)
+ implementation(Dependencies.KotlinX.coroutinesCore)
+ implementation(Dependencies.KotlinX.coroutinesAndroid)
+
+ implementation(Dependencies.Grpc.grpcOkHttp)
+ implementation(Dependencies.Grpc.grpcAndroid)
+ implementation(Dependencies.Grpc.grpcKotlinStub)
+ implementation(Dependencies.Grpc.protobufLite)
+ implementation(Dependencies.Grpc.protobufKotlinLite)
+
+ implementation(Dependencies.Arrow.core)
+ implementation(Dependencies.Arrow.optics)
+
+ testImplementation(project(Dependencies.Mullvad.commonTestLib))
+ testImplementation(Dependencies.Kotlin.test)
+ testImplementation(Dependencies.KotlinX.coroutinesTest)
+ testImplementation(Dependencies.MockK.core)
+ testImplementation(Dependencies.turbine)
+ testImplementation(Dependencies.junitApi)
+ testRuntimeOnly(Dependencies.junitEngine)
+ testImplementation(Dependencies.junitParams)
+}
diff --git a/android/lib/ipc/src/main/AndroidManifest.xml b/android/lib/daemon-grpc/src/main/AndroidManifest.xml
index cc947c5679..cc947c5679 100644
--- a/android/lib/ipc/src/main/AndroidManifest.xml
+++ b/android/lib/daemon-grpc/src/main/AndroidManifest.xml
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
new file mode 100644
index 0000000000..80b79b707a
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
@@ -0,0 +1,565 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc
+
+import android.net.LocalSocketAddress
+import android.util.Log
+import arrow.core.Either
+import arrow.optics.copy
+import arrow.optics.dsl.index
+import arrow.optics.typeclasses.Index
+import com.google.protobuf.BoolValue
+import com.google.protobuf.Empty
+import com.google.protobuf.StringValue
+import com.google.protobuf.UInt32Value
+import io.grpc.ConnectivityState
+import io.grpc.Status
+import io.grpc.StatusException
+import io.grpc.android.UdsChannelBuilder
+import java.net.InetAddress
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import mullvad_daemon.management_interface.ManagementInterface
+import mullvad_daemon.management_interface.ManagementServiceGrpcKt
+import net.mullvad.mullvadvpn.lib.common.constant.TAG
+import net.mullvad.mullvadvpn.lib.daemon.grpc.mapper.fromDomain
+import net.mullvad.mullvadvpn.lib.daemon.grpc.mapper.toDomain
+import net.mullvad.mullvadvpn.lib.daemon.grpc.util.LogInterceptor
+import net.mullvad.mullvadvpn.lib.daemon.grpc.util.connectivityFlow
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.AddSplitTunnelingAppError
+import net.mullvad.mullvadvpn.lib.model.AppId
+import net.mullvad.mullvadvpn.lib.model.AppVersionInfo as ModelAppVersionInfo
+import net.mullvad.mullvadvpn.lib.model.ClearAllOverridesError
+import net.mullvad.mullvadvpn.lib.model.ConnectError
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.CreateAccountError
+import net.mullvad.mullvadvpn.lib.model.CreateCustomListError
+import net.mullvad.mullvadvpn.lib.model.CustomList as ModelCustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.DeleteCustomListError
+import net.mullvad.mullvadvpn.lib.model.DeleteDeviceError
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceId
+import net.mullvad.mullvadvpn.lib.model.DeviceState as ModelDeviceState
+import net.mullvad.mullvadvpn.lib.model.DnsOptions as ModelDnsOptions
+import net.mullvad.mullvadvpn.lib.model.DnsOptions
+import net.mullvad.mullvadvpn.lib.model.DnsState as ModelDnsState
+import net.mullvad.mullvadvpn.lib.model.GetAccountDataError
+import net.mullvad.mullvadvpn.lib.model.GetAccountHistoryError
+import net.mullvad.mullvadvpn.lib.model.GetDeviceListError
+import net.mullvad.mullvadvpn.lib.model.GetDeviceStateError
+import net.mullvad.mullvadvpn.lib.model.LoginAccountError
+import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings as ModelObfuscationSettings
+import net.mullvad.mullvadvpn.lib.model.Ownership as ModelOwnership
+import net.mullvad.mullvadvpn.lib.model.PlayPurchase
+import net.mullvad.mullvadvpn.lib.model.PlayPurchaseInitError
+import net.mullvad.mullvadvpn.lib.model.PlayPurchasePaymentToken
+import net.mullvad.mullvadvpn.lib.model.PlayPurchaseVerifyError
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.lib.model.QuantumResistantState as ModelQuantumResistantState
+import net.mullvad.mullvadvpn.lib.model.RedeemVoucherError
+import net.mullvad.mullvadvpn.lib.model.RedeemVoucherSuccess
+import net.mullvad.mullvadvpn.lib.model.RelayConstraints
+import net.mullvad.mullvadvpn.lib.model.RelayItem
+import net.mullvad.mullvadvpn.lib.model.RelayItemId as ModelRelayItemId
+import net.mullvad.mullvadvpn.lib.model.RelayList as ModelRelayList
+import net.mullvad.mullvadvpn.lib.model.RelayList
+import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.RemoveSplitTunnelingAppError
+import net.mullvad.mullvadvpn.lib.model.SetAllowLanError
+import net.mullvad.mullvadvpn.lib.model.SetAutoConnectError
+import net.mullvad.mullvadvpn.lib.model.SetDnsOptionsError
+import net.mullvad.mullvadvpn.lib.model.SetObfuscationOptionsError
+import net.mullvad.mullvadvpn.lib.model.SetRelayLocationError
+import net.mullvad.mullvadvpn.lib.model.SetWireguardConstraintsError
+import net.mullvad.mullvadvpn.lib.model.SetWireguardMtuError
+import net.mullvad.mullvadvpn.lib.model.SetWireguardQuantumResistantError
+import net.mullvad.mullvadvpn.lib.model.Settings as ModelSettings
+import net.mullvad.mullvadvpn.lib.model.SettingsPatchError
+import net.mullvad.mullvadvpn.lib.model.TunnelState as ModelTunnelState
+import net.mullvad.mullvadvpn.lib.model.UnknownCustomListError
+import net.mullvad.mullvadvpn.lib.model.UpdateCustomListError
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
+import net.mullvad.mullvadvpn.lib.model.WireguardConstraints as ModelWireguardConstraints
+import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData as ModelWireguardEndpointData
+import net.mullvad.mullvadvpn.lib.model.addresses
+import net.mullvad.mullvadvpn.lib.model.customOptions
+import net.mullvad.mullvadvpn.lib.model.location
+import net.mullvad.mullvadvpn.lib.model.ownership
+import net.mullvad.mullvadvpn.lib.model.providers
+import net.mullvad.mullvadvpn.lib.model.relayConstraints
+import net.mullvad.mullvadvpn.lib.model.state
+import net.mullvad.mullvadvpn.lib.model.wireguardConstraints
+
+@Suppress("TooManyFunctions")
+class ManagementService(
+ rpcSocketPath: String,
+ private val extensiveLogging: Boolean,
+ private val scope: CoroutineScope,
+) {
+ private var job: Job? = null
+
+ private val channel =
+ UdsChannelBuilder.forPath(rpcSocketPath, LocalSocketAddress.Namespace.FILESYSTEM).build()
+
+ val connectionState: StateFlow<GrpcConnectivityState> =
+ channel
+ .connectivityFlow()
+ .map(ConnectivityState::toDomain)
+ .stateIn(scope, SharingStarted.Eagerly, channel.getState(false).toDomain())
+
+ private val grpc =
+ ManagementServiceGrpcKt.ManagementServiceCoroutineStub(channel)
+ .withExecutor(Dispatchers.IO.asExecutor())
+ .let {
+ if (extensiveLogging) {
+ it.withInterceptors(LogInterceptor())
+ } else it
+ }
+ .withWaitForReady()
+
+ private val _mutableDeviceState = MutableStateFlow<ModelDeviceState?>(null)
+ val deviceState: Flow<ModelDeviceState> = _mutableDeviceState.filterNotNull()
+
+ private val _mutableTunnelState = MutableStateFlow<ModelTunnelState?>(null)
+ val tunnelState: Flow<ModelTunnelState> = _mutableTunnelState.filterNotNull()
+
+ private val _mutableSettings = MutableStateFlow<ModelSettings?>(null)
+ val settings: Flow<ModelSettings> = _mutableSettings.filterNotNull()
+
+ private val _mutableVersionInfo = MutableStateFlow<ModelAppVersionInfo?>(null)
+ val versionInfo: Flow<ModelAppVersionInfo> = _mutableVersionInfo.filterNotNull()
+
+ private val _mutableRelayList = MutableStateFlow<RelayList?>(null)
+ val relayList: Flow<RelayList> = _mutableRelayList.filterNotNull()
+
+ val relayCountries: Flow<List<RelayItem.Location.Country>> =
+ relayList.mapNotNull { it.countries }
+
+ val wireguardEndpointData: Flow<ModelWireguardEndpointData> =
+ relayList.mapNotNull { it.wireguardEndpointData }
+
+ fun start() {
+ // Just to ensure that connection is set up since the connection won't be setup without a
+ // call to the daemon
+ if (job != null) {
+ error("ManagementService already started")
+ }
+
+ job = scope.launch { subscribeEvents() }
+ }
+
+ fun stop() {
+ job?.cancel(message = "ManagementService stopped")
+ ?: error("ManagementService already stopped")
+ job = null
+ }
+
+ private suspend fun subscribeEvents() =
+ withContext(Dispatchers.IO) {
+ launch {
+ grpc.eventsListen(Empty.getDefaultInstance()).collect { event ->
+ if (extensiveLogging) {
+ Log.d(TAG, "Event: $event")
+ }
+ @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
+ when (event.eventCase) {
+ ManagementInterface.DaemonEvent.EventCase.TUNNEL_STATE ->
+ _mutableTunnelState.update { event.tunnelState.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.SETTINGS ->
+ _mutableSettings.update { event.settings.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.RELAY_LIST ->
+ _mutableRelayList.update { event.relayList.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.VERSION_INFO ->
+ _mutableVersionInfo.update { event.versionInfo.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.DEVICE ->
+ _mutableDeviceState.update { event.device.newState.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.REMOVE_DEVICE -> {}
+ ManagementInterface.DaemonEvent.EventCase.EVENT_NOT_SET -> {}
+ ManagementInterface.DaemonEvent.EventCase.NEW_ACCESS_METHOD -> {}
+ }
+ }
+ }
+ getInitialServiceState()
+ }
+
+ suspend fun getDevice(): Either<GetDeviceStateError, ModelDeviceState> =
+ Either.catch { grpc.getDevice(Empty.getDefaultInstance()) }
+ .map { it.toDomain() }
+ .mapLeft { GetDeviceStateError.Unknown(it) }
+
+ suspend fun getDeviceList(token: AccountToken): Either<GetDeviceListError, List<Device>> =
+ Either.catch { grpc.listDevices(StringValue.of(token.value)) }
+ .map { it.devicesList.map(ManagementInterface.Device::toDomain) }
+ .mapLeft { GetDeviceListError.Unknown(it) }
+
+ suspend fun removeDevice(
+ token: AccountToken,
+ deviceId: DeviceId
+ ): Either<DeleteDeviceError, Unit> =
+ Either.catch {
+ grpc.removeDevice(
+ ManagementInterface.DeviceRemoval.newBuilder()
+ .setAccountToken(token.value)
+ .setDeviceId(deviceId.value.toString())
+ .build(),
+ )
+ }
+ .mapEmpty()
+ .mapLeft { DeleteDeviceError.Unknown(it) }
+
+ suspend fun connect(): Either<ConnectError, Boolean> =
+ Either.catch { grpc.connectTunnel(Empty.getDefaultInstance()).value }
+ .mapLeft(ConnectError::Unknown)
+
+ suspend fun disconnect(): Boolean = grpc.disconnectTunnel(Empty.getDefaultInstance()).value
+
+ suspend fun reconnect(): Boolean = grpc.reconnectTunnel(Empty.getDefaultInstance()).value
+
+ private suspend fun getTunnelState(): ModelTunnelState =
+ grpc.getTunnelState(Empty.getDefaultInstance()).toDomain()
+
+ private suspend fun getSettings(): ModelSettings =
+ grpc.getSettings(Empty.getDefaultInstance()).toDomain()
+
+ private suspend fun getDeviceState(): ModelDeviceState =
+ grpc.getDevice(Empty.getDefaultInstance()).toDomain()
+
+ private suspend fun getRelayList(): ModelRelayList =
+ grpc.getRelayLocations(Empty.getDefaultInstance()).toDomain()
+
+ private suspend fun getVersionInfo(): ModelAppVersionInfo =
+ grpc.getVersionInfo(Empty.getDefaultInstance()).toDomain()
+
+ suspend fun logoutAccount() {
+ grpc.logoutAccount(Empty.getDefaultInstance())
+ }
+
+ suspend fun loginAccount(accountToken: AccountToken): Either<LoginAccountError, Unit> =
+ Either.catch { grpc.loginAccount(StringValue.of(accountToken.value)) }
+ .mapLeftStatus {
+ when (it.status.code) {
+ Status.Code.UNAUTHENTICATED -> LoginAccountError.InvalidAccount
+ Status.Code.RESOURCE_EXHAUSTED ->
+ LoginAccountError.MaxDevicesReached(accountToken)
+ Status.Code.UNAVAILABLE -> LoginAccountError.RpcError
+ else -> LoginAccountError.Unknown(it)
+ }
+ }
+ .mapEmpty()
+
+ suspend fun clearAccountHistory() {
+ grpc.clearAccountHistory(Empty.getDefaultInstance())
+ }
+
+ suspend fun getAccountHistory(): Either<GetAccountHistoryError, AccountToken?> =
+ Either.catch {
+ val history = grpc.getAccountHistory(Empty.getDefaultInstance())
+ if (history.hasToken()) {
+ AccountToken(history.token.value)
+ } else {
+ null
+ }
+ }
+ .mapLeft(GetAccountHistoryError::Unknown)
+
+ private suspend fun getInitialServiceState() {
+ withContext(Dispatchers.IO) {
+ awaitAll(
+ async { _mutableTunnelState.update { getTunnelState() } },
+ async { _mutableDeviceState.update { getDeviceState() } },
+ async { _mutableSettings.update { getSettings() } },
+ async { _mutableVersionInfo.update { getVersionInfo() } },
+ async { _mutableRelayList.update { getRelayList() } },
+ )
+ }
+ }
+
+ suspend fun getAccountData(
+ accountToken: AccountToken
+ ): Either<GetAccountDataError, AccountData> =
+ Either.catch { grpc.getAccountData(StringValue.of(accountToken.value)).toDomain() }
+ .mapLeft(GetAccountDataError::Unknown)
+
+ suspend fun createAccount(): Either<CreateAccountError, AccountToken> =
+ Either.catch {
+ val accountTokenStringValue = grpc.createNewAccount(Empty.getDefaultInstance())
+ AccountToken(accountTokenStringValue.value)
+ }
+ .mapLeft(CreateAccountError::Unknown)
+
+ suspend fun setDnsOptions(dnsOptions: ModelDnsOptions): Either<SetDnsOptionsError, Unit> =
+ Either.catch { grpc.setDnsOptions(dnsOptions.fromDomain()) }
+ .mapLeft(SetDnsOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setDnsState(dnsState: ModelDnsState): Either<SetDnsOptionsError, Unit> =
+ Either.catch {
+ val currentDnsOptions = getSettings().tunnelOptions.dnsOptions
+ val updated = DnsOptions.state.set(currentDnsOptions, dnsState)
+ grpc.setDnsOptions(updated.fromDomain())
+ }
+ .mapLeft(SetDnsOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setCustomDns(index: Int, address: InetAddress): Either<SetDnsOptionsError, Unit> =
+ Either.catch {
+ val currentDnsOptions = getSettings().tunnelOptions.dnsOptions
+ val updatedDnsOptions =
+ DnsOptions.customOptions.addresses
+ .index(Index.list(), index)
+ .set(currentDnsOptions, address)
+
+ grpc.setDnsOptions(updatedDnsOptions.fromDomain())
+ }
+ .mapLeft(SetDnsOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun addCustomDns(address: InetAddress): Either<SetDnsOptionsError, Unit> =
+ Either.catch {
+ val currentDnsOptions = getSettings().tunnelOptions.dnsOptions
+ val updatedDnsOptions =
+ DnsOptions.customOptions.addresses.modify(currentDnsOptions) { it + address }
+ grpc.setDnsOptions(updatedDnsOptions.fromDomain())
+ }
+ .mapLeft(SetDnsOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun deleteCustomDns(address: InetAddress): Either<SetDnsOptionsError, Unit> =
+ Either.catch {
+ val currentDnsOptions = getSettings().tunnelOptions.dnsOptions
+ val updatedDnsOptions =
+ DnsOptions.customOptions.addresses.modify(currentDnsOptions) { it - address }
+ grpc.setDnsOptions(updatedDnsOptions.fromDomain())
+ }
+ .mapLeft(SetDnsOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setWireguardMtu(value: Int): Either<SetWireguardMtuError, Unit> =
+ Either.catch { grpc.setWireguardMtu(UInt32Value.of(value)) }
+ .mapLeft(SetWireguardMtuError::Unknown)
+ .mapEmpty()
+
+ suspend fun resetWireguardMtu(): Either<SetWireguardMtuError, Unit> =
+ Either.catch { grpc.setWireguardMtu(UInt32Value.newBuilder().clearValue().build()) }
+ .mapLeft(SetWireguardMtuError::Unknown)
+ .mapEmpty()
+
+ suspend fun setWireguardQuantumResistant(
+ value: ModelQuantumResistantState
+ ): Either<SetWireguardQuantumResistantError, Unit> =
+ Either.catch { grpc.setQuantumResistantTunnel(value.toDomain()) }
+ .mapLeft(SetWireguardQuantumResistantError::Unknown)
+ .mapEmpty()
+
+ // Todo needs to be more advanced
+ suspend fun setRelaySettings(value: RelaySettings) {
+ grpc.setRelaySettings(value.fromDomain())
+ }
+
+ suspend fun setObfuscationOptions(
+ value: ModelObfuscationSettings
+ ): Either<SetObfuscationOptionsError, Unit> =
+ Either.catch { grpc.setObfuscationSettings(value.fromDomain()) }
+ .mapLeft(SetObfuscationOptionsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setAutoConnect(isEnabled: Boolean): Either<SetAutoConnectError, Unit> =
+ Either.catch { grpc.setAutoConnect(BoolValue.of(isEnabled)) }
+ .mapLeft(SetAutoConnectError::Unknown)
+ .mapEmpty()
+
+ suspend fun setAllowLan(allow: Boolean): Either<SetAllowLanError, Unit> =
+ Either.catch { grpc.setAllowLan(BoolValue.of(allow)) }
+ .mapLeft(SetAllowLanError::Unknown)
+ .mapEmpty()
+
+ suspend fun setRelayLocation(location: ModelRelayItemId): Either<SetRelayLocationError, Unit> =
+ Either.catch {
+ val currentRelaySettings = getSettings().relaySettings
+ val updatedRelaySettings =
+ RelaySettings.relayConstraints.location.set(
+ currentRelaySettings,
+ Constraint.Only(location),
+ )
+ grpc.setRelaySettings(updatedRelaySettings.fromDomain())
+ }
+ .mapLeft(SetRelayLocationError::Unknown)
+ .mapEmpty()
+
+ suspend fun createCustomList(
+ name: CustomListName
+ ): Either<CreateCustomListError, CustomListId> =
+ Either.catch { grpc.createCustomList(StringValue.of(name.value)) }
+ .map { CustomListId(it.value) }
+ .mapLeftStatus {
+ when (it.status.code) {
+ Status.Code.ALREADY_EXISTS -> CustomListAlreadyExists
+ else -> UnknownCustomListError(it)
+ }
+ }
+
+ suspend fun updateCustomList(customList: ModelCustomList): Either<UpdateCustomListError, Unit> =
+ Either.catch { grpc.updateCustomList(customList.fromDomain()) }
+ .mapLeft(::UnknownCustomListError)
+ .mapEmpty()
+
+ suspend fun deleteCustomList(id: CustomListId): Either<DeleteCustomListError, Unit> =
+ Either.catch { grpc.deleteCustomList(StringValue.of(id.value)) }
+ .mapLeft(::UnknownCustomListError)
+ .mapEmpty()
+
+ suspend fun clearAllRelayOverrides(): Either<ClearAllOverridesError, Unit> =
+ Either.catch { grpc.clearAllRelayOverrides(Empty.getDefaultInstance()) }
+ .mapLeft(ClearAllOverridesError::Unknown)
+ .mapEmpty()
+
+ suspend fun applySettingsPatch(json: String): Either<SettingsPatchError, Unit> =
+ Either.catch { grpc.applyJsonSettings(StringValue.of(json)) }
+ .mapLeftStatus {
+ when (it.status.code) {
+ // Currently we only get invalid argument errors from daemon via gRPC
+ Status.Code.INVALID_ARGUMENT -> SettingsPatchError.ParsePatch
+ else -> SettingsPatchError.ApplyPatch
+ }
+ }
+ .mapEmpty()
+
+ suspend fun setWireguardConstraints(
+ value: ModelWireguardConstraints
+ ): Either<SetWireguardConstraintsError, Unit> =
+ Either.catch {
+ val relaySettings = getSettings().relaySettings
+ val updated =
+ RelaySettings.relayConstraints.wireguardConstraints.set(relaySettings, value)
+ grpc.setRelaySettings(updated.fromDomain())
+ }
+ .mapLeft(SetWireguardConstraintsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setOwnershipAndProviders(
+ ownershipConstraint: Constraint<ModelOwnership>,
+ providersConstraint: Constraint<Providers>
+ ): Either<SetWireguardConstraintsError, Unit> =
+ Either.catch {
+ val relaySettings = getSettings().relaySettings
+ val updated =
+ relaySettings.copy {
+ inside(RelaySettings.relayConstraints) {
+ RelayConstraints.providers set providersConstraint
+ RelayConstraints.ownership set ownershipConstraint
+ }
+ }
+ grpc.setRelaySettings(updated.fromDomain())
+ }
+ .mapLeft(SetWireguardConstraintsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setOwnership(
+ ownership: Constraint<ModelOwnership>
+ ): Either<SetWireguardConstraintsError, Unit> =
+ Either.catch {
+ val relaySettings = getSettings().relaySettings
+ val updated = RelaySettings.relayConstraints.ownership.set(relaySettings, ownership)
+ grpc.setRelaySettings(updated.fromDomain())
+ }
+ .mapLeft(SetWireguardConstraintsError::Unknown)
+ .mapEmpty()
+
+ suspend fun setProviders(
+ providersConstraint: Constraint<Providers>
+ ): Either<SetWireguardConstraintsError, Unit> =
+ Either.catch {
+ val relaySettings = getSettings().relaySettings
+ val updated =
+ RelaySettings.relayConstraints.providers.set(relaySettings, providersConstraint)
+ grpc.setRelaySettings(updated.fromDomain())
+ }
+ .mapLeft(SetWireguardConstraintsError::Unknown)
+ .mapEmpty()
+
+ suspend fun submitVoucher(voucher: String): Either<RedeemVoucherError, RedeemVoucherSuccess> =
+ Either.catch { grpc.submitVoucher(StringValue.of(voucher)).toDomain() }
+ .mapLeftStatus {
+ when (it.status.code) {
+ Status.Code.INVALID_ARGUMENT,
+ Status.Code.NOT_FOUND -> RedeemVoucherError.InvalidVoucher
+ Status.Code.ALREADY_EXISTS,
+ Status.Code.RESOURCE_EXHAUSTED -> RedeemVoucherError.VoucherAlreadyUsed
+ Status.Code.UNAVAILABLE -> RedeemVoucherError.RpcError
+ else -> RedeemVoucherError.Unknown(it)
+ }
+ }
+
+ suspend fun initializePlayPurchase(): Either<PlayPurchaseInitError, PlayPurchasePaymentToken> =
+ Either.catch { grpc.initPlayPurchase(Empty.getDefaultInstance()).toDomain() }
+ .mapLeft { PlayPurchaseInitError.OtherError }
+
+ suspend fun verifyPlayPurchase(purchase: PlayPurchase): Either<PlayPurchaseVerifyError, Unit> =
+ Either.catch { grpc.verifyPlayPurchase(purchase.fromDomain()) }
+ .mapLeft { PlayPurchaseVerifyError.OtherError }
+ .mapEmpty()
+
+ suspend fun addSplitTunnelingApp(app: AppId): Either<AddSplitTunnelingAppError, Unit> =
+ Either.catch { grpc.addSplitTunnelApp(StringValue.of(app.value)) }
+ .mapLeft(AddSplitTunnelingAppError::Unknown)
+ .mapEmpty()
+
+ suspend fun removeSplitTunnelingApp(app: AppId): Either<RemoveSplitTunnelingAppError, Unit> =
+ Either.catch { grpc.removeSplitTunnelApp(StringValue.of(app.value)) }
+ .mapLeft(RemoveSplitTunnelingAppError::Unknown)
+ .mapEmpty()
+
+ suspend fun setSplitTunnelingState(
+ enabled: Boolean
+ ): Either<RemoveSplitTunnelingAppError, Unit> =
+ Either.catch { grpc.setSplitTunnelState(BoolValue.of(enabled)) }
+ .mapLeft(RemoveSplitTunnelingAppError::Unknown)
+ .mapEmpty()
+
+ suspend fun getWebsiteAuthToken(): Either<Throwable, WebsiteAuthToken> =
+ Either.catch { grpc.getWwwAuthToken(Empty.getDefaultInstance()) }
+ .map { WebsiteAuthToken.fromString(it.value) }
+
+ private fun <A> Either<A, Empty>.mapEmpty() = map {}
+
+ private inline fun <B, C> Either<Throwable, B>.mapLeftStatus(
+ f: (StatusException) -> C
+ ): Either<C, B> = mapLeft {
+ if (it is StatusException) {
+ f(it)
+ } else {
+ throw it
+ }
+ }
+}
+
+sealed interface GrpcConnectivityState {
+ data object Connecting : GrpcConnectivityState
+
+ data object Ready : GrpcConnectivityState
+
+ data object Idle : GrpcConnectivityState
+
+ data object TransientFailure : GrpcConnectivityState
+
+ data object Shutdown : GrpcConnectivityState
+}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparator.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparator.kt
new file mode 100644
index 0000000000..a1b1d3b092
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparator.kt
@@ -0,0 +1,33 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc
+
+import net.mullvad.mullvadvpn.lib.model.RelayItem
+
+internal object RelayNameComparator : Comparator<RelayItem.Location.Relay> {
+ override fun compare(o1: RelayItem.Location.Relay, o2: RelayItem.Location.Relay): Int {
+ val partitions1 = o1.name.split(regex)
+ val partitions2 = o2.name.split(regex)
+ return if (partitions1.size > partitions2.size) partitions1 compareWith partitions2
+ else -(partitions2 compareWith partitions1)
+ }
+
+ private infix fun List<String>.compareWith(other: List<String>): Int {
+ this.forEachIndexed { index, s ->
+ if (other.size <= index) return 1
+ val partsCompareResult = compareStringOrInt(other[index], s)
+ if (partsCompareResult != 0) return partsCompareResult
+ }
+ return 0
+ }
+
+ private fun compareStringOrInt(s1: String, s2: String): Int {
+ val int1 = s1.toIntOrNull()
+ val int2 = s2.toIntOrNull()
+ return if (int1 == null || int2 == null || int1 == int2) {
+ s2.compareTo(s1)
+ } else {
+ int2.compareTo(int1)
+ }
+ }
+
+ private val regex = "(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)".toRegex()
+}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
new file mode 100644
index 0000000000..df4625228f
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
@@ -0,0 +1,140 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc.mapper
+
+import mullvad_daemon.management_interface.ManagementInterface
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions
+import net.mullvad.mullvadvpn.lib.model.DnsOptions
+import net.mullvad.mullvadvpn.lib.model.DnsState
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.PlayPurchase
+import net.mullvad.mullvadvpn.lib.model.PlayPurchasePaymentToken
+import net.mullvad.mullvadvpn.lib.model.Port
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.lib.model.RelayItemId
+import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
+
+internal fun Constraint<RelayItemId>.fromDomain(): ManagementInterface.LocationConstraint =
+ ManagementInterface.LocationConstraint.newBuilder()
+ .apply {
+ when (this@fromDomain) {
+ is Constraint.Any -> {}
+ is Constraint.Only -> {
+ when (val relayItemId = value) {
+ is CustomListId -> setCustomList(relayItemId.value)
+ is GeoLocationId -> setLocation(relayItemId.fromDomain())
+ }
+ }
+ }
+ }
+ .build()
+
+internal fun Constraint<Providers>.fromDomain(): List<String> =
+ when (this) {
+ is Constraint.Any -> emptyList()
+ is Constraint.Only -> value.providers.map { it.value }
+ }
+
+internal fun DnsOptions.fromDomain(): ManagementInterface.DnsOptions =
+ ManagementInterface.DnsOptions.newBuilder()
+ .setState(state.fromDomain())
+ .setCustomOptions(customOptions.fromDomain())
+ .setDefaultOptions(defaultOptions.fromDomain())
+ .build()
+
+internal fun DnsState.fromDomain(): ManagementInterface.DnsOptions.DnsState =
+ when (this) {
+ DnsState.Default -> ManagementInterface.DnsOptions.DnsState.DEFAULT
+ DnsState.Custom -> ManagementInterface.DnsOptions.DnsState.CUSTOM
+ }
+
+internal fun CustomDnsOptions.fromDomain(): ManagementInterface.CustomDnsOptions =
+ ManagementInterface.CustomDnsOptions.newBuilder()
+ .addAllAddresses(addresses.map { it.hostAddress })
+ .build()
+
+internal fun DefaultDnsOptions.fromDomain(): ManagementInterface.DefaultDnsOptions =
+ ManagementInterface.DefaultDnsOptions.newBuilder()
+ .setBlockAds(blockAds)
+ .setBlockGambling(blockGambling)
+ .setBlockMalware(blockMalware)
+ .setBlockTrackers(blockTrackers)
+ .setBlockAdultContent(blockAdultContent)
+ .setBlockSocialMedia(blockSocialMedia)
+ .build()
+
+internal fun ObfuscationSettings.fromDomain(): ManagementInterface.ObfuscationSettings =
+ ManagementInterface.ObfuscationSettings.newBuilder()
+ .setSelectedObfuscation(selectedObfuscation.toDomain())
+ .setUdp2Tcp(udp2tcp.toDomain())
+ .build()
+
+internal fun GeoLocationId.fromDomain(): ManagementInterface.GeographicLocationConstraint =
+ ManagementInterface.GeographicLocationConstraint.newBuilder()
+ .apply {
+ when (val id = this@fromDomain) {
+ is GeoLocationId.Country -> setCountry(id.countryCode)
+ is GeoLocationId.City -> setCountry(id.countryCode.countryCode).setCity(id.cityCode)
+ is GeoLocationId.Hostname ->
+ setCountry(id.country.countryCode)
+ .setCity(id.city.cityCode)
+ .setHostname(id.hostname)
+ }
+ }
+ .build()
+
+internal fun CustomList.fromDomain(): ManagementInterface.CustomList =
+ ManagementInterface.CustomList.newBuilder()
+ .setId(id.value)
+ .setName(name.value)
+ .addAllLocations(locations.map { it.fromDomain() })
+ .build()
+
+internal fun WireguardConstraints.fromDomain(): ManagementInterface.WireguardConstraints =
+ when (port) {
+ is Constraint.Any -> ManagementInterface.WireguardConstraints.newBuilder().build()
+ is Constraint.Only ->
+ ManagementInterface.WireguardConstraints.newBuilder()
+ .setPort((port as Constraint.Only<Port>).value.value)
+ .build()
+ }
+
+internal fun Ownership.fromDomain(): ManagementInterface.Ownership =
+ when (this) {
+ Ownership.MullvadOwned -> ManagementInterface.Ownership.MULLVAD_OWNED
+ Ownership.Rented -> ManagementInterface.Ownership.RENTED
+ }
+
+internal fun RelaySettings.fromDomain(): ManagementInterface.RelaySettings =
+ ManagementInterface.RelaySettings.newBuilder()
+ .setNormal(
+ ManagementInterface.NormalRelaySettings.newBuilder()
+ .setTunnelType(ManagementInterface.TunnelType.WIREGUARD)
+ .setWireguardConstraints(relayConstraints.wireguardConstraints.fromDomain())
+ .setOpenvpnConstraints(ManagementInterface.OpenvpnConstraints.getDefaultInstance())
+ .setLocation(relayConstraints.location.fromDomain())
+ .setOwnership(relayConstraints.ownership.fromDomain())
+ .addAllProviders(relayConstraints.providers.fromDomain())
+ .build()
+ )
+ .build()
+
+internal fun Constraint<Ownership>.fromDomain(): ManagementInterface.Ownership =
+ when (this) {
+ Constraint.Any -> ManagementInterface.Ownership.ANY
+ is Constraint.Only -> value.fromDomain()
+ }
+
+internal fun PlayPurchasePaymentToken.fromDomain(): ManagementInterface.PlayPurchasePaymentToken =
+ ManagementInterface.PlayPurchasePaymentToken.newBuilder().setToken(value).build()
+
+internal fun PlayPurchase.fromDomain(): ManagementInterface.PlayPurchase =
+ ManagementInterface.PlayPurchase.newBuilder()
+ .setPurchaseToken(purchaseToken.fromDomain())
+ .setProductId(productId)
+ .build()
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
new file mode 100644
index 0000000000..636e6c5176
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -0,0 +1,540 @@
+@file:Suppress("TooManyFunctions")
+
+package net.mullvad.mullvadvpn.lib.daemon.grpc.mapper
+
+import io.grpc.ConnectivityState
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.util.UUID
+import mullvad_daemon.management_interface.ManagementInterface
+import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState
+import net.mullvad.mullvadvpn.lib.daemon.grpc.RelayNameComparator
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.AccountId
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.AppId
+import net.mullvad.mullvadvpn.lib.model.AppVersionInfo
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceId
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.DnsOptions
+import net.mullvad.mullvadvpn.lib.model.DnsState
+import net.mullvad.mullvadvpn.lib.model.Endpoint
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.Mtu
+import net.mullvad.mullvadvpn.lib.model.ObfuscationEndpoint
+import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
+import net.mullvad.mullvadvpn.lib.model.ObfuscationType
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
+import net.mullvad.mullvadvpn.lib.model.PlayPurchasePaymentToken
+import net.mullvad.mullvadvpn.lib.model.Port
+import net.mullvad.mullvadvpn.lib.model.PortRange
+import net.mullvad.mullvadvpn.lib.model.Provider
+import net.mullvad.mullvadvpn.lib.model.ProviderId
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.lib.model.QuantumResistantState
+import net.mullvad.mullvadvpn.lib.model.RedeemVoucherSuccess
+import net.mullvad.mullvadvpn.lib.model.RelayConstraints
+import net.mullvad.mullvadvpn.lib.model.RelayItem
+import net.mullvad.mullvadvpn.lib.model.RelayItemId
+import net.mullvad.mullvadvpn.lib.model.RelayList
+import net.mullvad.mullvadvpn.lib.model.RelayOverride
+import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
+import net.mullvad.mullvadvpn.lib.model.Settings
+import net.mullvad.mullvadvpn.lib.model.SplitTunnelSettings
+import net.mullvad.mullvadvpn.lib.model.TransportProtocol
+import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
+import net.mullvad.mullvadvpn.lib.model.TunnelOptions
+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.WireguardTunnelOptions
+import org.joda.time.Instant
+
+internal fun ManagementInterface.TunnelState.toDomain(): TunnelState =
+ when (stateCase!!) {
+ ManagementInterface.TunnelState.StateCase.DISCONNECTED ->
+ TunnelState.Disconnected(
+ location =
+ with(disconnected) {
+ if (hasDisconnectedLocation()) {
+ disconnectedLocation.toDomain()
+ } else null
+ },
+ )
+ ManagementInterface.TunnelState.StateCase.CONNECTING ->
+ TunnelState.Connecting(
+ endpoint = connecting.relayInfo.tunnelEndpoint.toDomain(),
+ location =
+ with(connecting.relayInfo) {
+ if (hasLocation()) {
+ location.toDomain()
+ } else null
+ }
+ )
+ ManagementInterface.TunnelState.StateCase.CONNECTED ->
+ TunnelState.Connected(
+ endpoint = connected.relayInfo.tunnelEndpoint.toDomain(),
+ location =
+ with(connected.relayInfo) {
+ if (hasLocation()) {
+ location.toDomain()
+ } else {
+ null
+ }
+ }
+ )
+ ManagementInterface.TunnelState.StateCase.DISCONNECTING ->
+ TunnelState.Disconnecting(
+ actionAfterDisconnect = disconnecting.afterDisconnect.toDomain(),
+ )
+ ManagementInterface.TunnelState.StateCase.ERROR ->
+ TunnelState.Error(errorState = error.errorState.toDomain())
+ ManagementInterface.TunnelState.StateCase.STATE_NOT_SET ->
+ TunnelState.Disconnected(
+ location = disconnected.disconnectedLocation.toDomain(),
+ )
+ }
+
+internal fun ManagementInterface.GeoIpLocation.toDomain(): GeoIpLocation =
+ GeoIpLocation(
+ ipv4 =
+ if (hasIpv4()) {
+ InetAddress.getByName(ipv4)
+ } else {
+ null
+ },
+ ipv6 =
+ if (hasIpv6()) {
+ InetAddress.getByName(ipv6)
+ } else {
+ null
+ },
+ country = country,
+ city = city,
+ latitude = latitude,
+ longitude = longitude,
+ hostname = hostname
+ )
+
+internal fun ManagementInterface.TunnelEndpoint.toDomain(): TunnelEndpoint =
+ TunnelEndpoint(
+ endpoint =
+ with(address) {
+ val indexOfSeparator = indexOfLast { it == ':' }
+ val ipPart =
+ address.substring(0, indexOfSeparator).filter { it !in listOf('[', ']') }
+ val portPart = address.substring(indexOfSeparator + 1)
+
+ Endpoint(
+ address = InetSocketAddress(InetAddress.getByName(ipPart), portPart.toInt()),
+ protocol = protocol.toDomain()
+ )
+ },
+ quantumResistant = quantumResistant,
+ obfuscation =
+ if (hasObfuscation()) {
+ obfuscation.toDomain()
+ } else {
+ null
+ }
+ )
+
+internal fun ManagementInterface.ObfuscationEndpoint.toDomain(): ObfuscationEndpoint =
+ ObfuscationEndpoint(
+ endpoint =
+ Endpoint(address = InetSocketAddress(address, port), protocol = protocol.toDomain()),
+ obfuscationType = obfuscationType.toDomain()
+ )
+
+internal fun ManagementInterface.ObfuscationType.toDomain(): ObfuscationType =
+ when (this) {
+ ManagementInterface.ObfuscationType.UDP2TCP -> ObfuscationType.Udp2Tcp
+ ManagementInterface.ObfuscationType.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized obfuscation type")
+ }
+
+internal fun ManagementInterface.TransportProtocol.toDomain(): TransportProtocol =
+ when (this) {
+ ManagementInterface.TransportProtocol.TCP -> TransportProtocol.Tcp
+ ManagementInterface.TransportProtocol.UDP -> TransportProtocol.Udp
+ ManagementInterface.TransportProtocol.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized transport protocol")
+ }
+
+internal fun ManagementInterface.AfterDisconnect.toDomain(): ActionAfterDisconnect =
+ when (this) {
+ ManagementInterface.AfterDisconnect.NOTHING -> ActionAfterDisconnect.Nothing
+ ManagementInterface.AfterDisconnect.RECONNECT -> ActionAfterDisconnect.Reconnect
+ ManagementInterface.AfterDisconnect.BLOCK -> ActionAfterDisconnect.Block
+ ManagementInterface.AfterDisconnect.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized action after disconnect")
+ }
+
+internal fun ManagementInterface.ErrorState.toDomain(): ErrorState =
+ ErrorState(
+ cause =
+ when (cause!!) {
+ ManagementInterface.ErrorState.Cause.AUTH_FAILED ->
+ ErrorStateCause.AuthFailed(authFailedError.name)
+ ManagementInterface.ErrorState.Cause.IPV6_UNAVAILABLE ->
+ ErrorStateCause.Ipv6Unavailable
+ ManagementInterface.ErrorState.Cause.SET_FIREWALL_POLICY_ERROR ->
+ policyError.toDomain()
+ ManagementInterface.ErrorState.Cause.SET_DNS_ERROR -> ErrorStateCause.DnsError
+ ManagementInterface.ErrorState.Cause.START_TUNNEL_ERROR ->
+ ErrorStateCause.StartTunnelError
+ ManagementInterface.ErrorState.Cause.TUNNEL_PARAMETER_ERROR ->
+ ErrorStateCause.TunnelParameterError(parameterError.toDomain())
+ ManagementInterface.ErrorState.Cause.IS_OFFLINE -> ErrorStateCause.IsOffline
+ ManagementInterface.ErrorState.Cause.VPN_PERMISSION_DENIED ->
+ ErrorStateCause.VpnPermissionDenied
+ ManagementInterface.ErrorState.Cause.SPLIT_TUNNEL_ERROR ->
+ ErrorStateCause.StartTunnelError
+ ManagementInterface.ErrorState.Cause.UNRECOGNIZED,
+ ManagementInterface.ErrorState.Cause.NEED_FULL_DISK_PERMISSIONS,
+ ManagementInterface.ErrorState.Cause.CREATE_TUNNEL_DEVICE ->
+ throw IllegalArgumentException("Unrecognized error state cause")
+ },
+ isBlocking = !hasBlockingError()
+ )
+
+internal fun ManagementInterface.ErrorState.FirewallPolicyError.toDomain():
+ ErrorStateCause.FirewallPolicyError =
+ when (type!!) {
+ ManagementInterface.ErrorState.FirewallPolicyError.ErrorType.GENERIC ->
+ ErrorStateCause.FirewallPolicyError.Generic
+ ManagementInterface.ErrorState.FirewallPolicyError.ErrorType.LOCKED,
+ ManagementInterface.ErrorState.FirewallPolicyError.ErrorType.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized firewall policy error")
+ }
+
+internal fun ManagementInterface.ErrorState.GenerationError.toDomain(): ParameterGenerationError =
+ when (this) {
+ ManagementInterface.ErrorState.GenerationError.NO_MATCHING_RELAY ->
+ ParameterGenerationError.NoMatchingRelay
+ ManagementInterface.ErrorState.GenerationError.NO_MATCHING_BRIDGE_RELAY ->
+ ParameterGenerationError.NoMatchingBridgeRelay
+ ManagementInterface.ErrorState.GenerationError.NO_WIREGUARD_KEY ->
+ ParameterGenerationError.NoWireguardKey
+ ManagementInterface.ErrorState.GenerationError.CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR ->
+ ParameterGenerationError.CustomTunnelHostResultionError
+ ManagementInterface.ErrorState.GenerationError.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized parameter generation error")
+ }
+
+internal fun ManagementInterface.Settings.toDomain(): Settings =
+ Settings(
+ relaySettings = relaySettings.toDomain(),
+ obfuscationSettings = obfuscationSettings.toDomain(),
+ customLists = customLists.customListsList.map { it.toDomain() },
+ allowLan = allowLan,
+ autoConnect = autoConnect,
+ tunnelOptions = tunnelOptions.toDomain(),
+ relayOverrides = relayOverridesList.map { it.toDomain() },
+ showBetaReleases = showBetaReleases,
+ splitTunnelSettings = splitTunnel.toDomain()
+ )
+
+internal fun ManagementInterface.RelayOverride.toDomain(): RelayOverride =
+ RelayOverride(
+ hostname = hostname,
+ ipv4AddressIn = if (hasIpv4AddrIn()) InetAddress.getByName(ipv4AddrIn) else null,
+ ipv6AddressIn = if (hasIpv6AddrIn()) InetAddress.getByName(ipv6AddrIn) else null
+ )
+
+internal fun ManagementInterface.RelaySettings.toDomain(): RelaySettings =
+ when (endpointCase) {
+ ManagementInterface.RelaySettings.EndpointCase.CUSTOM ->
+ throw IllegalArgumentException("CustomTunnelEndpoint is not supported")
+ ManagementInterface.RelaySettings.EndpointCase.NORMAL -> RelaySettings(normal.toDomain())
+ ManagementInterface.RelaySettings.EndpointCase.ENDPOINT_NOT_SET ->
+ throw IllegalArgumentException("RelaySettings endpoint not set")
+ else -> throw NullPointerException("RelaySettings endpoint is null")
+ }
+
+internal fun ManagementInterface.NormalRelaySettings.toDomain(): RelayConstraints =
+ RelayConstraints(
+ location = location.toDomain(),
+ providers = providersList.toDomain(),
+ ownership = ownership.toDomain(),
+ wireguardConstraints = wireguardConstraints.toDomain()
+ )
+
+internal fun ManagementInterface.LocationConstraint.toDomain(): Constraint<RelayItemId> =
+ when (typeCase) {
+ ManagementInterface.LocationConstraint.TypeCase.CUSTOM_LIST ->
+ Constraint.Only(CustomListId(customList))
+ ManagementInterface.LocationConstraint.TypeCase.LOCATION ->
+ Constraint.Only(location.toDomain())
+ ManagementInterface.LocationConstraint.TypeCase.TYPE_NOT_SET -> Constraint.Any
+ else -> throw IllegalArgumentException("Location constraint type is null")
+ }
+
+@Suppress("ReturnCount")
+internal fun ManagementInterface.GeographicLocationConstraint.toDomain(): GeoLocationId {
+ val country = GeoLocationId.Country(country)
+ if (!hasCity()) {
+ return country
+ }
+
+ val city = GeoLocationId.City(country, city)
+ if (!hasHostname()) {
+ return city
+ }
+ return GeoLocationId.Hostname(city, hostname)
+}
+
+internal fun List<String>.toDomain(): Constraint<Providers> =
+ if (isEmpty()) Constraint.Any else Constraint.Only(Providers(map { ProviderId(it) }.toSet()))
+
+internal fun ManagementInterface.WireguardConstraints.toDomain(): WireguardConstraints =
+ WireguardConstraints(
+ port =
+ if (hasPort()) {
+ Constraint.Only(Port(port))
+ } else {
+ Constraint.Any
+ },
+ )
+
+internal fun ManagementInterface.Ownership.toDomain(): Constraint<Ownership> =
+ when (this) {
+ ManagementInterface.Ownership.ANY -> Constraint.Any
+ ManagementInterface.Ownership.MULLVAD_OWNED -> Constraint.Only(Ownership.MullvadOwned)
+ ManagementInterface.Ownership.RENTED -> Constraint.Only(Ownership.Rented)
+ ManagementInterface.Ownership.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized ownership")
+ }
+
+internal fun ManagementInterface.ObfuscationSettings.toDomain(): ObfuscationSettings =
+ ObfuscationSettings(
+ selectedObfuscation = selectedObfuscation.toDomain(),
+ udp2tcp = udp2Tcp.toDomain()
+ )
+
+internal fun ManagementInterface.ObfuscationSettings.SelectedObfuscation.toDomain():
+ SelectedObfuscation =
+ when (this) {
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.AUTO -> SelectedObfuscation.Auto
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.OFF -> SelectedObfuscation.Off
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.UDP2TCP ->
+ SelectedObfuscation.Udp2Tcp
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized selected obfuscation")
+ }
+
+internal fun ManagementInterface.Udp2TcpObfuscationSettings.toDomain(): Udp2TcpObfuscationSettings =
+ if (hasPort()) {
+ Udp2TcpObfuscationSettings(Constraint.Only(Port(port)))
+ } else {
+ Udp2TcpObfuscationSettings(Constraint.Any)
+ }
+
+internal fun ManagementInterface.CustomList.toDomain(): CustomList =
+ CustomList(
+ id = CustomListId(id),
+ name = CustomListName.fromString(name),
+ locations = locationsList.map { it.toDomain() }
+ )
+
+internal fun ManagementInterface.TunnelOptions.toDomain(): TunnelOptions =
+ TunnelOptions(wireguard = wireguard.toDomain(), dnsOptions = dnsOptions.toDomain())
+
+internal fun ManagementInterface.TunnelOptions.WireguardOptions.toDomain(): WireguardTunnelOptions =
+ WireguardTunnelOptions(
+ mtu = if (hasMtu()) Mtu(mtu) else null,
+ quantumResistant = quantumResistant.toDomain(),
+ )
+
+internal fun ManagementInterface.QuantumResistantState.toDomain(): QuantumResistantState =
+ when (state) {
+ ManagementInterface.QuantumResistantState.State.AUTO -> QuantumResistantState.Auto
+ ManagementInterface.QuantumResistantState.State.ON -> QuantumResistantState.On
+ ManagementInterface.QuantumResistantState.State.OFF -> QuantumResistantState.Off
+ ManagementInterface.QuantumResistantState.State.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized quantum resistant state")
+ else -> throw NullPointerException("Quantum resistant state is null")
+ }
+
+internal fun ManagementInterface.DnsOptions.toDomain(): DnsOptions =
+ DnsOptions(
+ state = state.toDomain(),
+ defaultOptions = defaultOptions.toDomain(),
+ customOptions = customOptions.toDomain()
+ )
+
+internal fun ManagementInterface.DnsOptions.DnsState.toDomain(): DnsState =
+ when (this) {
+ ManagementInterface.DnsOptions.DnsState.DEFAULT -> DnsState.Default
+ ManagementInterface.DnsOptions.DnsState.CUSTOM -> DnsState.Custom
+ ManagementInterface.DnsOptions.DnsState.UNRECOGNIZED ->
+ throw IllegalArgumentException("Unrecognized dns state")
+ }
+
+internal fun ManagementInterface.DefaultDnsOptions.toDomain() =
+ DefaultDnsOptions(
+ blockAds = blockAds,
+ blockMalware = blockMalware,
+ blockAdultContent = blockAdultContent,
+ blockGambling = blockGambling,
+ blockSocialMedia = blockSocialMedia,
+ blockTrackers = blockTrackers
+ )
+
+internal fun ManagementInterface.CustomDnsOptions.toDomain() =
+ CustomDnsOptions(addressesList.map { InetAddress.getByName(it) })
+
+internal fun QuantumResistantState.toDomain(): ManagementInterface.QuantumResistantState =
+ ManagementInterface.QuantumResistantState.newBuilder()
+ .setState(
+ when (this) {
+ QuantumResistantState.Auto -> ManagementInterface.QuantumResistantState.State.AUTO
+ QuantumResistantState.On -> ManagementInterface.QuantumResistantState.State.ON
+ QuantumResistantState.Off -> ManagementInterface.QuantumResistantState.State.OFF
+ }
+ )
+ .build()
+
+internal fun SelectedObfuscation.toDomain():
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation =
+ when (this) {
+ SelectedObfuscation.Udp2Tcp ->
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.UDP2TCP
+ SelectedObfuscation.Auto -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.AUTO
+ SelectedObfuscation.Off -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.OFF
+ }
+
+internal fun Udp2TcpObfuscationSettings.toDomain(): ManagementInterface.Udp2TcpObfuscationSettings =
+ when (val port = port) {
+ is Constraint.Any ->
+ ManagementInterface.Udp2TcpObfuscationSettings.newBuilder().clearPort().build()
+ is Constraint.Only ->
+ ManagementInterface.Udp2TcpObfuscationSettings.newBuilder()
+ .setPort(port.value.value)
+ .build()
+ }
+
+internal fun ManagementInterface.AppVersionInfo.toDomain(): AppVersionInfo =
+ AppVersionInfo(
+ supported = supported,
+ suggestedUpgrade = if (hasSuggestedUpgrade()) suggestedUpgrade else null
+ )
+
+internal fun ConnectivityState.toDomain(): GrpcConnectivityState =
+ when (this) {
+ ConnectivityState.CONNECTING -> GrpcConnectivityState.Connecting
+ ConnectivityState.READY -> GrpcConnectivityState.Ready
+ ConnectivityState.IDLE -> GrpcConnectivityState.Idle
+ ConnectivityState.TRANSIENT_FAILURE -> GrpcConnectivityState.TransientFailure
+ ConnectivityState.SHUTDOWN -> GrpcConnectivityState.Shutdown
+ }
+
+internal fun ManagementInterface.RelayList.toDomain(): RelayList =
+ RelayList(countriesList.toDomain(), wireguard.toDomain())
+
+internal fun ManagementInterface.WireguardEndpointData.toDomain(): WireguardEndpointData =
+ WireguardEndpointData(portRangesList.map { it.toDomain() })
+
+internal fun ManagementInterface.PortRange.toDomain(): PortRange = PortRange(first..last)
+
+/**
+ * Convert from a list of ManagementInterface.RelayListCountry to a model.RelayList. Non-wireguard
+ * relays are filtered out. So are also cities that only contains non-wireguard relays and countries
+ * that does not have any cities. Countries, cities and relays are ordered by name.
+ */
+internal fun List<ManagementInterface.RelayListCountry>.toDomain():
+ List<RelayItem.Location.Country> =
+ map(ManagementInterface.RelayListCountry::toDomain)
+ .filter { it.cities.isNotEmpty() }
+ .sortedBy { it.name }
+
+internal fun ManagementInterface.RelayListCountry.toDomain(): RelayItem.Location.Country {
+ val countryCode = GeoLocationId.Country(code)
+ return RelayItem.Location.Country(
+ countryCode,
+ name,
+ false,
+ citiesList
+ .map { city -> city.toDomain(countryCode) }
+ .filter { it.relays.isNotEmpty() }
+ .sortedBy { it.name }
+ )
+}
+
+internal fun ManagementInterface.RelayListCity.toDomain(
+ countryCode: GeoLocationId.Country
+): RelayItem.Location.City {
+ val cityCode = GeoLocationId.City(countryCode, code)
+ return RelayItem.Location.City(
+ name = name,
+ id = cityCode,
+ expanded = false,
+ relays =
+ relaysList
+ .filter { it.endpointType == ManagementInterface.Relay.RelayType.WIREGUARD }
+ .map { it.toDomain(cityCode) }
+ .sortedWith(RelayNameComparator)
+ )
+}
+
+internal fun ManagementInterface.Relay.toDomain(
+ cityCode: GeoLocationId.City
+): RelayItem.Location.Relay =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(cityCode, hostname),
+ active = active,
+ provider =
+ Provider(
+ ProviderId(provider),
+ ownership = if (owned) Ownership.MullvadOwned else Ownership.Rented
+ )
+ )
+
+internal fun ManagementInterface.Device.toDomain(): Device =
+ Device(DeviceId.fromString(id), name, Instant.ofEpochSecond(created.seconds).toDateTime())
+
+internal fun ManagementInterface.DeviceState.toDomain(): DeviceState =
+ when (state) {
+ ManagementInterface.DeviceState.State.LOGGED_IN ->
+ DeviceState.LoggedIn(AccountToken(device.accountToken), device.device.toDomain())
+ ManagementInterface.DeviceState.State.LOGGED_OUT -> DeviceState.LoggedOut
+ ManagementInterface.DeviceState.State.REVOKED -> DeviceState.Revoked
+ ManagementInterface.DeviceState.State.UNRECOGNIZED ->
+ throw IllegalArgumentException("Non valid device state")
+ else -> throw NullPointerException("Device state is null")
+ }
+
+internal fun ManagementInterface.AccountData.toDomain(): AccountData =
+ AccountData(
+ AccountId(UUID.fromString(id)),
+ expiryDate = Instant.ofEpochSecond(expiry.seconds).toDateTime()
+ )
+
+internal fun ManagementInterface.VoucherSubmission.toDomain(): RedeemVoucherSuccess =
+ RedeemVoucherSuccess(
+ timeAdded = secondsAdded,
+ newExpiryDate = Instant.ofEpochSecond(newExpiry.seconds).toDateTime()
+ )
+
+internal fun ManagementInterface.SplitTunnelSettings.toDomain(): SplitTunnelSettings =
+ SplitTunnelSettings(
+ enabled = enableExclusions,
+ excludedApps = appsList.map { AppId(it) }.toSet()
+ )
+
+internal fun ManagementInterface.PlayPurchasePaymentToken.toDomain(): PlayPurchasePaymentToken =
+ PlayPurchasePaymentToken(value = token)
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/LogInterceptor.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/LogInterceptor.kt
new file mode 100644
index 0000000000..fde87ecdd5
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/LogInterceptor.kt
@@ -0,0 +1,20 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc.util
+
+import android.util.Log
+import io.grpc.CallOptions
+import io.grpc.Channel
+import io.grpc.ClientCall
+import io.grpc.ClientInterceptor
+import io.grpc.MethodDescriptor
+import net.mullvad.mullvadvpn.lib.common.constant.TAG
+
+internal class LogInterceptor : ClientInterceptor {
+ override fun <ReqT : Any?, RespT : Any?> interceptCall(
+ method: MethodDescriptor<ReqT, RespT>?,
+ callOptions: CallOptions?,
+ next: Channel?
+ ): ClientCall<ReqT, RespT> {
+ Log.d(TAG, "Intercepted call: ${method?.fullMethodName}")
+ return next!!.newCall(method, callOptions)
+ }
+}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/ManagedChannel.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/ManagedChannel.kt
new file mode 100644
index 0000000000..3f98ae93d8
--- /dev/null
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/util/ManagedChannel.kt
@@ -0,0 +1,24 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc.util
+
+import io.grpc.ConnectivityState
+import io.grpc.ManagedChannel
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.isActive
+
+internal fun ManagedChannel.connectivityFlow(): Flow<ConnectivityState> {
+ return callbackFlow {
+ var currentState = getState(false)
+ send(currentState)
+
+ while (isActive) {
+ currentState =
+ suspendCoroutine<ConnectivityState> {
+ notifyWhenStateChanged(currentState) { it.resume(getState(false)) }
+ }
+ send(currentState)
+ }
+ }
+}
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
new file mode 100644
index 0000000000..42cf745510
--- /dev/null
+++ b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
@@ -0,0 +1,282 @@
+package net.mullvad.mullvadvpn.lib.daemon.grpc
+
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.Provider
+import net.mullvad.mullvadvpn.lib.model.ProviderId
+import net.mullvad.mullvadvpn.lib.model.RelayItem
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+class RelayNameComparatorTest {
+
+ @AfterEach
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `given two relays with same prefix but different numbers comparator should return lowest number first`() {
+ val relay9 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se9-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ ),
+ )
+ val relay10 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se10-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relay9 assertOrderBothDirection relay10
+ }
+
+ @Test
+ fun `given two relays with same name with number in name comparator should return 0`() {
+ val relay9a =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se9-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay9b =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se9-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
+ assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
+ }
+
+ @Test
+ fun `comparator should be able to handle name of only numbers`() {
+ val relay001 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "001"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay1 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "1"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay3 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "3"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay100 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "100"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relay001 assertOrderBothDirection relay1
+ relay001 assertOrderBothDirection relay3
+ relay1 assertOrderBothDirection relay3
+ relay3 assertOrderBothDirection relay100
+ }
+
+ @Test
+ fun `given two relays with same name and without number comparator should return 0`() {
+ val relay9a =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay9b =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
+ assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
+ }
+
+ @Test
+ fun `given two relays with leading zeroes comparator should return lowest number first`() {
+ val relay001 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se001-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay005 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se005-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relay001 assertOrderBothDirection relay005
+ }
+
+ @Test
+ fun `given 4 relays comparator should sort by prefix then number`() {
+ val relayAr2 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "ar2-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relayAr8 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "ar8-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relaySe5 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se5-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relaySe10 =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se10-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relayAr2 assertOrderBothDirection relayAr8
+ relayAr8 assertOrderBothDirection relaySe5
+ relaySe5 assertOrderBothDirection relaySe10
+ }
+
+ @Test
+ fun `given two relays with same prefix and number comparator should sort by suffix`() {
+ val relay2c =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se2-cloud"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay2w =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se2-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relay2c assertOrderBothDirection relay2w
+ }
+
+ @Test
+ fun `given two relays with same prefix, but one with no suffix, the one with no suffix should come first`() {
+ val relay22a =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se22"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+ val relay22b =
+ RelayItem.Location.Relay(
+ id = GeoLocationId.Hostname(city = mockk(), "se22-wireguard"),
+ active = false,
+ provider =
+ Provider(
+ providerId = ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
+ )
+
+ relay22a assertOrderBothDirection relay22b
+ }
+
+ private infix fun RelayItem.Location.Relay.assertOrderBothDirection(
+ other: RelayItem.Location.Relay
+ ) {
+ assertTrue(RelayNameComparator.compare(this, other) < 0)
+ assertTrue(RelayNameComparator.compare(other, this) > 0)
+ }
+}
diff --git a/android/lib/ipc/build.gradle.kts b/android/lib/intent-provider/build.gradle.kts
index 35fa3c4f1e..f63a9c7f69 100644
--- a/android/lib/ipc/build.gradle.kts
+++ b/android/lib/intent-provider/build.gradle.kts
@@ -2,17 +2,13 @@ plugins {
id(Dependencies.Plugin.androidLibraryId)
id(Dependencies.Plugin.kotlinAndroidId)
id(Dependencies.Plugin.kotlinParcelizeId)
- id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5
}
android {
- namespace = "net.mullvad.mullvadvpn.lib.ipc"
+ namespace = "net.mullvad.mullvadvpn.lib.intent"
compileSdk = Versions.Android.compileSdkVersion
- defaultConfig {
- minSdk = Versions.Android.minSdkVersion
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
+ defaultConfig { minSdk = Versions.Android.minSdkVersion }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
@@ -26,16 +22,10 @@ android {
abortOnError = true
warningsAsErrors = true
}
+ buildFeatures { buildConfig = true }
}
dependencies {
- implementation(project(Dependencies.Mullvad.modelLib))
-
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.KotlinX.coroutinesAndroid)
-
- androidTestImplementation(Dependencies.junitApi)
- androidTestImplementation(Dependencies.junitEngine)
- androidTestImplementation(Dependencies.AndroidX.testRunner)
- androidTestImplementation(Dependencies.Kotlin.test)
}
diff --git a/android/lib/intent-provider/src/main/AndroidManifest.xml b/android/lib/intent-provider/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..cc947c5679
--- /dev/null
+++ b/android/lib/intent-provider/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest />
diff --git a/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt b/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt
new file mode 100644
index 0000000000..86ad970b5d
--- /dev/null
+++ b/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt
@@ -0,0 +1,16 @@
+package net.mullvad.mullvadvpn.lib.intent
+
+import android.content.Intent
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class IntentProvider {
+ private val _intents = MutableStateFlow<Intent?>(null)
+ val intents: Flow<Intent?> = _intents
+
+ fun setStartIntent(intent: Intent?) {
+ _intents.tryEmit(intent)
+ }
+
+ fun getLatestIntent(): Intent? = _intents.value
+}
diff --git a/android/lib/ipc/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlowTest.kt b/android/lib/ipc/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlowTest.kt
deleted file mode 100644
index a125af6059..0000000000
--- a/android/lib/ipc/src/androidTest/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlowTest.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Bundle
-import android.os.Looper
-import android.os.Message
-import android.os.Parcelable
-import kotlin.test.assertEquals
-import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.flow.toList
-import kotlinx.coroutines.runBlocking
-import kotlinx.parcelize.Parcelize
-import org.junit.jupiter.api.Test
-
-class HandlerFlowTest {
- val looper by lazy { Looper.getMainLooper() }
-
- val handler: HandlerFlow<Data?> by lazy {
- HandlerFlow(looper) { message -> message.data.getParcelable(DATA_KEY) }
- }
-
- @Test
- fun test_message_extraction() {
- sendMessage(Data(1))
- sendMessage(Data(2))
- sendMessage(Data(3))
-
- val extractedData = runBlocking { handler.take(3).toList() }
-
- assertEquals(listOf(Data(1), Data(2), Data(3)), extractedData)
- }
-
- private fun sendMessage(messageData: Data) {
- val message =
- Message().apply { data = Bundle().apply { putParcelable(DATA_KEY, messageData) } }
-
- handler.handleMessage(message)
- }
-
- companion object {
- const val DATA_KEY = "data"
-
- @Parcelize data class Data(val id: Int) : Parcelable
- }
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt
deleted file mode 100644
index efaa1b78f8..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/DispatchingHandler.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Handler
-import android.os.Looper
-import android.os.Message
-import android.util.Log
-import java.util.concurrent.locks.ReentrantReadWriteLock
-import kotlin.concurrent.withLock
-import kotlin.reflect.KClass
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-
-class DispatchingHandler<T : Any>(looper: Looper, private val extractor: (Message) -> T?) :
- Handler(looper), MessageDispatcher<T> {
- private val handlers = HashMap<KClass<out T>, (T) -> Unit>()
- private val lock = ReentrantReadWriteLock()
-
- private val _parsedMessages =
- MutableSharedFlow<T>(extraBufferCapacity = MESSAGES_BUFFER_CAPACITY)
- val parsedMessages = _parsedMessages.asSharedFlow()
-
- @Deprecated("Use parsedMessages instead.")
- override fun <V : T> registerHandler(variant: KClass<V>, handler: (V) -> Unit) {
- lock.writeLock().withLock {
- handlers.put(variant) { instance -> @Suppress("UNCHECKED_CAST") handler(instance as V) }
- }
- }
-
- override fun handleMessage(message: Message) {
- lock.readLock().withLock {
- val instance = extractor(message)
-
- if (instance != null) {
- val handler = handlers.get(instance::class)
-
- handler?.invoke(instance)
- _parsedMessages.tryEmit(instance)
- } else {
- Log.e("mullvad", "Dispatching handler received an unexpected message")
- }
- }
- }
-
- fun onDestroy() {
- lock.writeLock().withLock { handlers.clear() }
-
- removeCallbacksAndMessages(null)
- }
-
- companion object {
- private const val MESSAGES_BUFFER_CAPACITY = 10
- }
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
deleted file mode 100644
index 36ea17036e..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Messenger
-import kotlinx.parcelize.Parcelize
-import net.mullvad.mullvadvpn.model.AccountCreationResult
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.AccountHistory
-import net.mullvad.mullvadvpn.model.CreateCustomListResult
-import net.mullvad.mullvadvpn.model.DeviceListEvent
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.LoginResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
-import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
-import net.mullvad.mullvadvpn.model.RelayList
-import net.mullvad.mullvadvpn.model.RemoveDeviceResult
-import net.mullvad.mullvadvpn.model.Settings
-import net.mullvad.mullvadvpn.model.SettingsPatchError
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.model.UpdateCustomListResult
-
-// Events that can be sent from the service
-sealed class Event : Message.EventMessage() {
- override val messageKey = MESSAGE_KEY
-
- @Parcelize data class AccountCreationEvent(val result: AccountCreationResult) : Event()
-
- @Parcelize data class AccountExpiryEvent(val expiry: AccountExpiry) : Event()
-
- @Parcelize data class AccountHistoryEvent(val history: AccountHistory) : Event()
-
- @Parcelize
- data class AppVersionInfo(val versionInfo: net.mullvad.mullvadvpn.model.AppVersionInfo?) :
- Event()
-
- @Parcelize data class AuthToken(val token: String?) : Event()
-
- @Parcelize data class CurrentVersion(val version: String?) : Event()
-
- @Parcelize data class DeviceStateEvent(val newState: DeviceState) : Event()
-
- @Parcelize data class DeviceListUpdate(val event: DeviceListEvent) : Event()
-
- @Parcelize
- data class DeviceRemovalEvent(val deviceId: String, val result: RemoveDeviceResult) : Event()
-
- @Parcelize data class ListenerReady(val connection: Messenger, val listenerId: Int) : Event()
-
- @Parcelize data class LoginEvent(val result: LoginResult) : Event()
-
- @Parcelize data class NewRelayList(val relayList: RelayList?) : Event()
-
- @Parcelize data class SettingsUpdate(val settings: Settings?) : Event()
-
- @Parcelize data class SplitTunnelingUpdate(val excludedApps: List<String>?) : Event()
-
- @Parcelize data class TunnelStateChange(val tunnelState: TunnelState) : Event()
-
- @Parcelize
- data class VoucherSubmissionResult(
- val voucher: String,
- val result: net.mullvad.mullvadvpn.model.VoucherSubmissionResult
- ) : Event()
-
- @Parcelize data class PlayPurchaseInitResultEvent(val result: PlayPurchaseInitResult) : Event()
-
- @Parcelize
- data class PlayPurchaseVerifyResultEvent(val result: PlayPurchaseVerifyResult) : Event()
-
- @Parcelize object VpnPermissionRequest : Event()
-
- @Parcelize data class CreateCustomListResultEvent(val result: CreateCustomListResult) : Event()
-
- @Parcelize data class UpdateCustomListResultEvent(val result: UpdateCustomListResult) : Event()
-
- @Parcelize data class ExportJsonSettingsResult(val json: String) : Event()
-
- @Parcelize data class ApplyJsonSettingsResult(val error: SettingsPatchError?) : Event()
-
- companion object {
- private const val MESSAGE_KEY = "event"
-
- fun fromMessage(message: android.os.Message): Event? = fromMessage(message, MESSAGE_KEY)
- }
-}
-
-typealias EventDispatcher = MessageDispatcher<Event>
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt
deleted file mode 100644
index 7b839a3658..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/HandlerFlow.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Handler
-import android.os.Looper
-import android.os.Message
-import android.util.Log
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedSendChannelException
-import kotlinx.coroutines.channels.trySendBlocking
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.flow.consumeAsFlow
-import kotlinx.coroutines.flow.onCompletion
-
-class HandlerFlow<T>(looper: Looper, private val extractor: (Message) -> T) :
- Handler(looper), Flow<T> {
- private val channel = Channel<T>(Channel.UNLIMITED)
- private val flow = channel.consumeAsFlow().onCompletion { removeCallbacksAndMessages(null) }
-
- @InternalCoroutinesApi
- override suspend fun collect(collector: FlowCollector<T>) = flow.collect(collector)
-
- override fun handleMessage(message: Message) {
- val extractedData = extractor(message)
-
- try {
- channel.trySendBlocking(extractedData)
- } catch (exception: ClosedSendChannelException) {
- Log.w("mullvad", "Received a message after HandlerFlow was closed", exception)
- removeCallbacksAndMessages(null)
- } catch (exception: CancellationException) {
- Log.w("mullvad", "Received a message after HandlerFlow was cancelled", exception)
- removeCallbacksAndMessages(null)
- }
- }
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt
deleted file mode 100644
index 7cc293b373..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Message.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Bundle
-import android.os.Message as RawMessage
-import android.os.Parcelable
-
-sealed class Message(private val messageId: Int) : Parcelable {
- abstract class EventMessage : Message(1)
-
- abstract class RequestMessage : Message(2)
-
- protected abstract val messageKey: String
-
- val message: RawMessage
- get() =
- RawMessage.obtain().also { message ->
- message.what = messageId
- message.data = Bundle()
- message.data.putParcelable(messageKey, this)
- }
-
- companion object {
- internal fun <T : Parcelable> fromMessage(message: RawMessage, key: String): T? {
- val data = message.data
-
- data.classLoader = Message::class.java.classLoader
-
- return data.getParcelable(key)
- }
- }
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt
deleted file mode 100644
index 8bb6703479..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageDispatcher.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import kotlin.reflect.KClass
-
-interface MessageDispatcher<T : Any> {
- fun <V : T> registerHandler(variant: KClass<V>, handler: (V) -> Unit)
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageHandler.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageHandler.kt
deleted file mode 100644
index 04de35e3bd..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/MessageHandler.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import kotlin.reflect.KClass
-import kotlinx.coroutines.flow.Flow
-
-interface MessageHandler {
- fun <R : Event> events(klass: KClass<R>): Flow<R>
-
- fun trySendRequest(request: Request): Boolean
-}
-
-inline fun <reified R : Event> MessageHandler.events(): Flow<R> {
- return this.events(R::class)
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt
deleted file mode 100644
index 4bcf871acc..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc
-
-import android.os.Message as RawMessage
-import android.os.Messenger
-import java.net.InetAddress
-import kotlinx.parcelize.Parcelize
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.CustomList
-import net.mullvad.mullvadvpn.model.DnsOptions
-import net.mullvad.mullvadvpn.model.LocationConstraint
-import net.mullvad.mullvadvpn.model.ObfuscationSettings
-import net.mullvad.mullvadvpn.model.Ownership
-import net.mullvad.mullvadvpn.model.PlayPurchase
-import net.mullvad.mullvadvpn.model.Providers
-import net.mullvad.mullvadvpn.model.QuantumResistantState
-import net.mullvad.mullvadvpn.model.RelayOverride
-import net.mullvad.mullvadvpn.model.WireguardConstraints
-
-// Requests that the service can handle
-sealed class Request : Message.RequestMessage() {
- override val messageKey = MESSAGE_KEY
-
- @Parcelize
- @Deprecated("Use SetDnsOptions")
- data class AddCustomDnsServer(val address: InetAddress) : Request()
-
- @Parcelize object Connect : Request()
-
- @Parcelize object CreateAccount : Request()
-
- @Parcelize object Disconnect : Request()
-
- @Parcelize data class ExcludeApp(val packageName: String) : Request()
-
- @Parcelize object FetchAccountExpiry : Request()
-
- @Parcelize object FetchAccountHistory : Request()
-
- @Parcelize object FetchAuthToken : Request()
-
- @Parcelize data class IncludeApp(val packageName: String) : Request()
-
- @Parcelize data class Login(val account: String?) : Request()
-
- @Parcelize object RefreshDeviceState : Request()
-
- @Parcelize object GetDevice : Request()
-
- @Parcelize data class GetDeviceList(val accountToken: String) : Request()
-
- @Parcelize data class RemoveDevice(val accountToken: String, val deviceId: String) : Request()
-
- @Parcelize object Logout : Request()
-
- @Parcelize object PersistExcludedApps : Request()
-
- @Parcelize object Reconnect : Request()
-
- @Parcelize data class RegisterListener(val listener: Messenger) : Request()
-
- @Parcelize object ClearAccountHistory : Request()
-
- @Parcelize
- @Deprecated("Use SetDnsOptions")
- data class RemoveCustomDnsServer(val address: InetAddress) : Request()
-
- @Parcelize
- @Deprecated("Use SetDnsOptions")
- data class ReplaceCustomDnsServer(val oldAddress: InetAddress, val newAddress: InetAddress) :
- Request()
-
- @Parcelize data class SetAllowLan(val allow: Boolean) : Request()
-
- @Parcelize data class SetAutoConnect(val autoConnect: Boolean) : Request()
-
- @Parcelize
- @Deprecated("Use SetDnsOptions")
- data class SetEnableCustomDns(val enable: Boolean) : Request()
-
- @Parcelize data class SetEnableSplitTunneling(val enable: Boolean) : Request()
-
- @Parcelize data class SetRelayLocation(val locationConstraint: LocationConstraint) : Request()
-
- @Parcelize data class SetWireGuardMtu(val mtu: Int?) : Request()
-
- @Parcelize data class SubmitVoucher(val voucher: String) : Request()
-
- @Parcelize data object InitPlayPurchase : Request()
-
- @Parcelize data class VerifyPlayPurchase(val playPurchase: PlayPurchase) : Request()
-
- @Parcelize data class UnregisterListener(val listenerId: Int) : Request()
-
- @Parcelize data class VpnPermissionResponse(val isGranted: Boolean) : Request()
-
- @Parcelize data class SetDnsOptions(val dnsOptions: DnsOptions) : Request()
-
- @Parcelize data class SetObfuscationSettings(val settings: ObfuscationSettings?) : Request()
-
- @Parcelize
- data class SetWireguardConstraints(val wireguardConstraints: WireguardConstraints) : Request()
-
- @Parcelize
- data class SetWireGuardQuantumResistant(val quantumResistant: QuantumResistantState) :
- Request()
-
- @Parcelize data object FetchRelayList : Request()
-
- @Parcelize
- data class SetOwnershipAndProviders(
- val ownership: Constraint<Ownership>,
- val providers: Constraint<Providers>
- ) : Request()
-
- @Parcelize data class CreateCustomList(val name: String) : Request()
-
- @Parcelize data class DeleteCustomList(val id: String) : Request()
-
- @Parcelize data class UpdateCustomList(val customList: CustomList) : Request()
-
- @Parcelize data object ClearAllRelayOverrides : Request()
-
- @Parcelize data class ApplyJsonSettings(val json: String) : Request()
-
- @Parcelize data object ExportJsonSettings : Request()
-
- @Parcelize data class SetRelayOverride(val override: RelayOverride) : Request()
-
- companion object {
- private const val MESSAGE_KEY = "request"
-
- fun fromMessage(message: RawMessage): Request? = fromMessage(message, MESSAGE_KEY)
- }
-}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/extensions/MessengerExtensions.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/extensions/MessengerExtensions.kt
deleted file mode 100644
index 26cade5cb4..0000000000
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/extensions/MessengerExtensions.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package net.mullvad.mullvadvpn.lib.ipc.extensions
-
-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/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt
index 6ce7690bc5..4047783825 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt
@@ -18,9 +18,9 @@ import net.mullvad.mullvadvpn.lib.map.internal.MAX_ANIMATION_MILLIS
import net.mullvad.mullvadvpn.lib.map.internal.MAX_MULTIPLIER_PEAK_TIMING
import net.mullvad.mullvadvpn.lib.map.internal.MIN_ANIMATION_MILLIS
import net.mullvad.mullvadvpn.lib.map.internal.SHORT_ANIMATION_CUTOFF_MILLIS
-import net.mullvad.mullvadvpn.model.LatLong
-import net.mullvad.mullvadvpn.model.Latitude
-import net.mullvad.mullvadvpn.model.Longitude
+import net.mullvad.mullvadvpn.lib.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.Latitude
+import net.mullvad.mullvadvpn.lib.model.Longitude
@Composable
fun animatedCameraPosition(
diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt
index a143a63cb8..b1ea1144f9 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt
@@ -13,7 +13,7 @@ import net.mullvad.mullvadvpn.lib.map.data.GlobeColors
import net.mullvad.mullvadvpn.lib.map.data.MapViewState
import net.mullvad.mullvadvpn.lib.map.data.Marker
import net.mullvad.mullvadvpn.lib.map.internal.MapGLSurfaceView
-import net.mullvad.mullvadvpn.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.LatLong
@Composable
fun Map(
diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/CameraPosition.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/CameraPosition.kt
index d837bcadfc..b66b0ea657 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/CameraPosition.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/CameraPosition.kt
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.lib.map.data
import androidx.compose.runtime.Immutable
-import net.mullvad.mullvadvpn.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.LatLong
@Immutable
data class CameraPosition(val latLong: LatLong, val zoom: Float, val verticalBias: Float)
diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/Marker.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/Marker.kt
index 9f464612f1..4d26348d45 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/Marker.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/Marker.kt
@@ -1,7 +1,7 @@
package net.mullvad.mullvadvpn.lib.map.data
import androidx.compose.runtime.Immutable
-import net.mullvad.mullvadvpn.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.LatLong
@Immutable
data class Marker(
diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt
index 41ac903fb1..887e64bebd 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt
@@ -13,7 +13,7 @@ import net.mullvad.mullvadvpn.lib.map.data.LocationMarkerColors
import net.mullvad.mullvadvpn.lib.map.data.MapViewState
import net.mullvad.mullvadvpn.lib.map.internal.shapes.Globe
import net.mullvad.mullvadvpn.lib.map.internal.shapes.LocationMarker
-import net.mullvad.mullvadvpn.model.toRadians
+import net.mullvad.mullvadvpn.lib.model.toRadians
internal class MapGLRenderer(private val resources: Resources) : GLSurfaceView.Renderer {
diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes/LocationMarker.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes/LocationMarker.kt
index 9d03a540c5..c67a0a1bb7 100644
--- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes/LocationMarker.kt
+++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes/LocationMarker.kt
@@ -12,7 +12,7 @@ import net.mullvad.mullvadvpn.lib.map.internal.VERTEX_COMPONENT_SIZE
import net.mullvad.mullvadvpn.lib.map.internal.initArrayBuffer
import net.mullvad.mullvadvpn.lib.map.internal.initShaderProgram
import net.mullvad.mullvadvpn.lib.map.internal.toFloatArray
-import net.mullvad.mullvadvpn.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.LatLong
internal class LocationMarker(val colors: LocationMarkerColors) {
diff --git a/android/lib/model/build.gradle.kts b/android/lib/model/build.gradle.kts
index 7264c6041a..28a5804b5f 100644
--- a/android/lib/model/build.gradle.kts
+++ b/android/lib/model/build.gradle.kts
@@ -3,10 +3,11 @@ plugins {
id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5
id(Dependencies.Plugin.kotlinAndroidId)
id(Dependencies.Plugin.kotlinParcelizeId)
+ id(Dependencies.Plugin.ksp) version Versions.Plugin.ksp
}
android {
- namespace = "net.mullvad.mullvadvpn.model"
+ namespace = "net.mullvad.mullvadvpn.lib.model"
compileSdk = Versions.Android.compileSdkVersion
defaultConfig {
@@ -29,11 +30,12 @@ android {
}
dependencies {
- implementation(project(Dependencies.Mullvad.talpidLib))
-
implementation(Dependencies.jodaTime)
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.KotlinX.coroutinesAndroid)
+ implementation(Dependencies.Arrow.core)
+ implementation(Dependencies.Arrow.optics)
+ ksp(Dependencies.Arrow.opticsKsp)
// Test dependencies
testRuntimeOnly(Dependencies.junitEngine)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt
new file mode 100644
index 0000000000..60395721d8
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import org.joda.time.DateTime
+
+data class AccountData(
+ val id: AccountId,
+ val expiryDate: DateTime,
+)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountId.kt
new file mode 100644
index 0000000000..75550259fd
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountId.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import java.util.UUID
+
+@JvmInline
+value class AccountId(val value: UUID) {
+ companion object {
+ fun fromString(value: String) = AccountId(UUID.fromString(value))
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountToken.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountToken.kt
new file mode 100644
index 0000000000..d03a0d6721
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountToken.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@JvmInline @Parcelize value class AccountToken(val value: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ActionAfterDisconnect.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ActionAfterDisconnect.kt
new file mode 100644
index 0000000000..531fc1c073
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ActionAfterDisconnect.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class ActionAfterDisconnect {
+ Nothing,
+ Block,
+ Reconnect
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddSplitTunnelingAppError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddSplitTunnelingAppError.kt
new file mode 100644
index 0000000000..338162db8c
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddSplitTunnelingAppError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+interface AddSplitTunnelingAppError {
+ data class Unknown(val throwable: Throwable) : AddSplitTunnelingAppError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppId.kt
new file mode 100644
index 0000000000..0663b530a1
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppId.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline value class AppId(val value: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppVersionInfo.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppVersionInfo.kt
new file mode 100644
index 0000000000..9af168bf28
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AppVersionInfo.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class AppVersionInfo(val supported: Boolean, val suggestedUpgrade: String?)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/BuildVersion.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/BuildVersion.kt
new file mode 100644
index 0000000000..980ea23961
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/BuildVersion.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class BuildVersion(val name: String, val code: Int)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ClearAllOverridesError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ClearAllOverridesError.kt
new file mode 100644
index 0000000000..ce1bc0af12
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ClearAllOverridesError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface ClearAllOverridesError {
+ data class Unknown(val throwable: Throwable) : ClearAllOverridesError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt
new file mode 100644
index 0000000000..307a235314
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ConnectError.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface ConnectError {
+ data class Unknown(val throwable: Throwable) : ConnectError
+
+ data object NoVpnPermission : ConnectError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Constraint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Constraint.kt
new file mode 100644
index 0000000000..95e7d95154
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Constraint.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+sealed interface Constraint<out T> {
+ data object Any : Constraint<Nothing>
+
+ @optics
+ data class Only<T>(val value: T) : Constraint<T> {
+ companion object
+ }
+
+ fun getOrNull(): T? =
+ when (this) {
+ Any -> null
+ is Only -> value
+ }
+
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateAccountError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateAccountError.kt
new file mode 100644
index 0000000000..eeeaf11fca
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateAccountError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed class CreateAccountError {
+ data class Unknown(val error: Throwable) : CreateAccountError()
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateCustomListError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateCustomListError.kt
new file mode 100644
index 0000000000..adbac22d9b
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CreateCustomListError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface CreateCustomListError
+
+data object CustomListAlreadyExists : CreateCustomListError
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomDnsOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomDnsOptions.kt
new file mode 100644
index 0000000000..4fd64b2892
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomDnsOptions.kt
@@ -0,0 +1,9 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+import java.net.InetAddress
+
+@optics
+data class CustomDnsOptions(val addresses: List<InetAddress>) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomList.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomList.kt
new file mode 100644
index 0000000000..ed43ac1097
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomList.kt
@@ -0,0 +1,12 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class CustomList(
+ val id: CustomListId,
+ val name: CustomListName,
+ val locations: List<GeoLocationId>
+) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomListName.kt
index 5822eec2b3..186d74dc92 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/CustomListName.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DefaultDnsOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DefaultDnsOptions.kt
index 69f4d4d220..6979320ce6 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DefaultDnsOptions.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DefaultDnsOptions.kt
@@ -1,9 +1,8 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
+import arrow.optics.optics
-@Parcelize
+@optics
data class DefaultDnsOptions(
val blockAds: Boolean = false,
val blockTrackers: Boolean = false,
@@ -11,7 +10,7 @@ data class DefaultDnsOptions(
val blockAdultContent: Boolean = false,
val blockGambling: Boolean = false,
val blockSocialMedia: Boolean = false,
-) : Parcelable {
+) {
fun isAnyBlockerEnabled(): Boolean {
return blockAds ||
blockTrackers ||
@@ -20,4 +19,6 @@ data class DefaultDnsOptions(
blockGambling ||
blockSocialMedia
}
+
+ companion object
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteCustomListError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteCustomListError.kt
new file mode 100644
index 0000000000..d9c93c87cf
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteCustomListError.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface DeleteCustomListError
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteDeviceError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteDeviceError.kt
new file mode 100644
index 0000000000..1c6c54bcf0
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeleteDeviceError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface DeleteDeviceError {
+ data class Unknown(val error: Throwable) : DeleteDeviceError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt
new file mode 100644
index 0000000000..e8303f0eca
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt
@@ -0,0 +1,12 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+import net.mullvad.mullvadvpn.lib.model.extensions.startCase
+import org.joda.time.DateTime
+
+@Parcelize
+data class Device(val id: DeviceId, private val name: String, val creationDate: DateTime) :
+ Parcelable {
+ fun displayName(): String = name.startCase()
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceId.kt
new file mode 100644
index 0000000000..863d15fd67
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceId.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import java.util.UUID
+import kotlinx.parcelize.Parcelize
+
+@JvmInline
+@Parcelize
+value class DeviceId(val value: UUID) : Parcelable {
+ companion object {
+ fun fromString(value: String): DeviceId = DeviceId(UUID.fromString(value))
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt
new file mode 100644
index 0000000000..4546cd46b3
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+sealed class DeviceState : Parcelable {
+ @Parcelize
+ data class LoggedIn(val accountToken: AccountToken, val device: Device) : DeviceState()
+
+ @Parcelize data object LoggedOut : DeviceState()
+
+ @Parcelize data object Revoked : DeviceState()
+
+ fun displayName(): String? {
+ return (this as? LoggedIn)?.device?.displayName()
+ }
+
+ fun token(): AccountToken? {
+ return (this as? LoggedIn)?.accountToken
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsOptions.kt
index 1ce3acc095..ae27e47457 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsOptions.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsOptions.kt
@@ -1,11 +1,12 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
+import arrow.optics.optics
-@Parcelize
+@optics
data class DnsOptions(
val state: DnsState,
val defaultOptions: DefaultDnsOptions,
val customOptions: CustomDnsOptions
-) : Parcelable
+) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsState.kt
index 9c8677ba7d..4bf053eef1 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DnsState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DnsState.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
enum class DnsState {
Default,
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/EnableSplitTunnelingError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/EnableSplitTunnelingError.kt
new file mode 100644
index 0000000000..43a4cc41b1
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/EnableSplitTunnelingError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+interface EnableSplitTunnelingError {
+ data class Unknown(val throwable: Throwable) : EnableSplitTunnelingError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Endpoint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Endpoint.kt
new file mode 100644
index 0000000000..4eae8b08ec
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Endpoint.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import java.net.InetSocketAddress
+
+data class Endpoint(val address: InetSocketAddress, val protocol: TransportProtocol)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorState.kt
new file mode 100644
index 0000000000..fb7673b7b5
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorState.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class ErrorState(val cause: ErrorStateCause, val isBlocking: Boolean)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt
new file mode 100644
index 0000000000..0ba63a4b08
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ErrorStateCause.kt
@@ -0,0 +1,34 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import java.net.InetAddress
+
+sealed class ErrorStateCause {
+ class AuthFailed(private val reason: String?) : ErrorStateCause() {
+ fun isCausedByExpiredAccount(): Boolean {
+ return reason == AUTH_FAILED_REASON_EXPIRED_ACCOUNT
+ }
+
+ companion object {
+ private const val AUTH_FAILED_REASON_EXPIRED_ACCOUNT = "[EXPIRED_ACCOUNT]"
+ }
+ }
+
+ data object Ipv6Unavailable : ErrorStateCause()
+
+ sealed class FirewallPolicyError : ErrorStateCause() {
+ data object Generic : FirewallPolicyError()
+ }
+
+ data object DnsError : ErrorStateCause()
+
+ // Regression
+ data class InvalidDnsServers(val addresses: List<InetAddress>) : ErrorStateCause()
+
+ data object StartTunnelError : ErrorStateCause()
+
+ data class TunnelParameterError(val error: ParameterGenerationError) : ErrorStateCause()
+
+ data object IsOffline : ErrorStateCause()
+
+ data object VpnPermissionDenied : ErrorStateCause()
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GeoIpLocation.kt
index 625de76b29..3334b458d7 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GeoIpLocation.kt
@@ -1,10 +1,7 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
import java.net.InetAddress
-import kotlinx.parcelize.Parcelize
-@Parcelize
data class GeoIpLocation(
val ipv4: InetAddress?,
val ipv6: InetAddress?,
@@ -13,4 +10,4 @@ data class GeoIpLocation(
val latitude: Double,
val longitude: Double,
val hostname: String?,
-) : Parcelable
+)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountDataError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountDataError.kt
new file mode 100644
index 0000000000..6f3ba64848
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountDataError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetAccountDataError {
+ data class Unknown(val error: Throwable) : GetAccountDataError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountHistoryError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountHistoryError.kt
new file mode 100644
index 0000000000..7803a98ad1
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetAccountHistoryError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetAccountHistoryError {
+ data class Unknown(val error: Throwable) : GetAccountHistoryError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceListError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceListError.kt
new file mode 100644
index 0000000000..bcad016580
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceListError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetDeviceListError {
+ data class Unknown(val error: Throwable) : GetDeviceListError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceStateError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceStateError.kt
new file mode 100644
index 0000000000..675973ee1e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetDeviceStateError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetDeviceStateError {
+ data class Unknown(val error: Throwable) : GetDeviceStateError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt
index d6749a16a2..19f757ffc3 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt
@@ -1,9 +1,9 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sqrt
-import net.mullvad.mullvadvpn.model.Latitude.Companion.mean
+import net.mullvad.mullvadvpn.lib.model.Latitude.Companion.mean
data class LatLong(val latitude: Latitude, val longitude: Longitude) {
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Latitude.kt
index 21d113f3bc..9b0cc7fbbe 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Latitude.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Latitude.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.absoluteValue
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ListDevicesError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ListDevicesError.kt
new file mode 100644
index 0000000000..6530450d42
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ListDevicesError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+interface ListDevicesError {
+ data class Unknown(val throwable: Throwable) : ListDevicesError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LoginAccountError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LoginAccountError.kt
new file mode 100644
index 0000000000..1c58f80bee
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LoginAccountError.kt
@@ -0,0 +1,15 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class LoginAccountError : Parcelable {
+ data object InvalidAccount : LoginAccountError()
+
+ data class MaxDevicesReached(val accountToken: AccountToken) : LoginAccountError()
+
+ data object RpcError : LoginAccountError()
+
+ data class Unknown(val error: Throwable) : LoginAccountError()
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Longitude.kt
index 9f73a6ff17..b772801da7 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Longitude.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Longitude.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.absoluteValue
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Mtu.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Mtu.kt
new file mode 100644
index 0000000000..68b4b71bd9
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Mtu.kt
@@ -0,0 +1,28 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import arrow.core.Either
+import arrow.core.raise.either
+import arrow.core.raise.ensure
+import kotlinx.parcelize.Parcelize
+
+@JvmInline
+@Parcelize
+value class Mtu(val value: Int) : Parcelable {
+ companion object {
+ fun fromString(value: String): Either<ParseMtuError, Mtu> = either {
+ val number = value.toIntOrNull() ?: raise(ParseMtuError.NotANumber)
+ ensure(number in MIN_VALUE..MAX_VALUE) { ParseMtuError.OutOfRange(number) }
+ Mtu(number)
+ }
+
+ private const val MIN_VALUE = 1280
+ private const val MAX_VALUE = 1420
+ }
+}
+
+sealed interface ParseMtuError {
+ data object NotANumber : ParseMtuError
+
+ data class OutOfRange(val number: Int) : ParseMtuError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt
new file mode 100644
index 0000000000..5dda03aa9d
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt
@@ -0,0 +1,25 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import org.joda.time.Duration
+
+sealed interface Notification {
+ val actions: List<NotificationAction>
+ val ongoing: Boolean
+ val channelId: NotificationChannelId
+
+ data class Tunnel(
+ override val channelId: NotificationChannelId,
+ val state: NotificationTunnelState,
+ override val actions: List<NotificationAction.Tunnel>,
+ override val ongoing: Boolean,
+ ) : Notification
+
+ data class AccountExpiry(
+ override val channelId: NotificationChannelId,
+ override val actions: List<NotificationAction.AccountExpiry>,
+ val websiteAuthToken: WebsiteAuthToken?,
+ val durationUntilExpiry: Duration
+ ) : Notification {
+ override val ongoing: Boolean = false
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt
new file mode 100644
index 0000000000..ec938a9fbf
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationAction.kt
@@ -0,0 +1,20 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface NotificationAction {
+
+ sealed interface AccountExpiry : NotificationAction {
+ data object Open : AccountExpiry
+ }
+
+ sealed interface Tunnel : NotificationAction {
+ data object Connect : Tunnel
+
+ data object Disconnect : Tunnel
+
+ data object Cancel : Tunnel
+
+ data object Dismiss : Tunnel
+
+ data object RequestPermission : Tunnel
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannel.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannel.kt
new file mode 100644
index 0000000000..166c20b826
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannel.kt
@@ -0,0 +1,15 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface NotificationChannel {
+ val id: NotificationChannelId
+
+ data object TunnelUpdates : NotificationChannel {
+ private const val CHANNEL_ID = "vpn_tunnel_status"
+ override val id: NotificationChannelId = NotificationChannelId(CHANNEL_ID)
+ }
+
+ data object AccountUpdates : NotificationChannel {
+ private const val CHANNEL_ID = "mullvad_account_time"
+ override val id: NotificationChannelId = NotificationChannelId(CHANNEL_ID)
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannelId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannelId.kt
new file mode 100644
index 0000000000..c4231deb8c
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationChannelId.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline value class NotificationChannelId(val value: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationId.kt
new file mode 100644
index 0000000000..9c20bf9420
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationId.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline value class NotificationId(val value: Int)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt
new file mode 100644
index 0000000000..fffe86c247
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationTunnelState.kt
@@ -0,0 +1,25 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface NotificationTunnelState {
+ data class Disconnected(val hasVpnPermission: Boolean) : NotificationTunnelState
+
+ data object Connecting : NotificationTunnelState
+
+ data object Connected : NotificationTunnelState
+
+ data object Reconnecting : NotificationTunnelState
+
+ data object Disconnecting : NotificationTunnelState
+
+ sealed interface Error : NotificationTunnelState {
+ data object DeviceOffline : Error
+
+ data object Blocking : Error
+
+ data object VpnPermissionDenied : Error
+
+ data object AlwaysOnVpn : Error
+
+ data object Critical : Error
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationUpdate.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationUpdate.kt
new file mode 100644
index 0000000000..00d64cbc3e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NotificationUpdate.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface NotificationUpdate<out D> {
+ val notificationId: NotificationId
+
+ data class Notify<D>(override val notificationId: NotificationId, val value: D) :
+ NotificationUpdate<D>
+
+ data class Cancel(override val notificationId: NotificationId) : NotificationUpdate<Nothing>
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationEndpoint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationEndpoint.kt
new file mode 100644
index 0000000000..020ef8e5c1
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationEndpoint.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class ObfuscationEndpoint(val endpoint: Endpoint, val obfuscationType: ObfuscationType)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ObfuscationSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationSettings.kt
index 19b5c0e5f2..b8a26973a2 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ObfuscationSettings.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationSettings.kt
@@ -1,10 +1,11 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
+import arrow.optics.optics
-@Parcelize
+@optics
data class ObfuscationSettings(
val selectedObfuscation: SelectedObfuscation,
val udp2tcp: Udp2TcpObfuscationSettings
-) : Parcelable
+) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt
new file mode 100644
index 0000000000..cd71d645af
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class ObfuscationType {
+ Udp2Tcp
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Ownership.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Ownership.kt
new file mode 100644
index 0000000000..5257f944d3
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Ownership.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class Ownership {
+ MullvadOwned,
+ Rented
+}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt
index b1504c676f..476aed1407 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt
@@ -1,4 +1,4 @@
-package net.mullvad.talpid.tunnel
+package net.mullvad.mullvadvpn.lib.model
enum class ParameterGenerationError {
NoMatchingRelay,
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchase.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchase.kt
new file mode 100644
index 0000000000..9384f9f5b8
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchase.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class PlayPurchase(val productId: String, val purchaseToken: PlayPurchasePaymentToken)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseInitError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseInitError.kt
new file mode 100644
index 0000000000..6326bab8e8
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseInitError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class PlayPurchaseInitError {
+ OtherError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchasePaymentToken.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchasePaymentToken.kt
new file mode 100644
index 0000000000..bfcae64d45
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchasePaymentToken.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline value class PlayPurchasePaymentToken(val value: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseVerifyError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseVerifyError.kt
new file mode 100644
index 0000000000..dc06b8ffbf
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PlayPurchaseVerifyError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class PlayPurchaseVerifyError {
+ OtherError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt
new file mode 100644
index 0000000000..bcb5a8dd99
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@JvmInline @Parcelize value class Port(val value: Int) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PortRange.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PortRange.kt
new file mode 100644
index 0000000000..77767a1011
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/PortRange.kt
@@ -0,0 +1,30 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcel
+import android.os.Parcelable
+import kotlinx.parcelize.Parceler
+import kotlinx.parcelize.Parcelize
+import kotlinx.parcelize.TypeParceler
+
+@JvmInline
+@Parcelize
+@TypeParceler<IntRange, IntRangeParceler>
+value class PortRange(val value: IntRange) : Parcelable {
+ operator fun contains(port: Port): Boolean = port.value in value
+
+ fun toFormattedString(): String =
+ if (value.first == value.last) {
+ value.first.toString()
+ } else {
+ "${value.first}-${value.last}"
+ }
+}
+
+object IntRangeParceler : Parceler<IntRange> {
+ override fun create(parcel: Parcel) = IntRange(parcel.readInt(), parcel.readInt())
+
+ override fun IntRange.write(parcel: Parcel, flags: Int) {
+ parcel.writeInt(start)
+ parcel.writeInt(endInclusive)
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Provider.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Provider.kt
new file mode 100644
index 0000000000..e704e9554d
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Provider.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class Provider(val providerId: ProviderId, val ownership: Ownership)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ProviderId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ProviderId.kt
new file mode 100644
index 0000000000..cc23c3e9b6
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ProviderId.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline value class ProviderId(val value: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Providers.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Providers.kt
new file mode 100644
index 0000000000..73cf9facdb
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Providers.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class Providers(val providers: Set<ProviderId>) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/QuantumResistantState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/QuantumResistantState.kt
new file mode 100644
index 0000000000..c77dab72d3
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/QuantumResistantState.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class QuantumResistantState {
+ Auto,
+ On,
+ Off
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherError.kt
new file mode 100644
index 0000000000..d14a2f236b
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherError.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed class RedeemVoucherError {
+ data object InvalidVoucher : RedeemVoucherError()
+
+ data object VoucherAlreadyUsed : RedeemVoucherError()
+
+ data object RpcError : RedeemVoucherError()
+
+ data class Unknown(val error: Throwable) : RedeemVoucherError()
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt
new file mode 100644
index 0000000000..9c81042b8c
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import org.joda.time.DateTime
+
+data class RedeemVoucherSuccess(val timeAdded: Long, val newExpiryDate: DateTime)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayConstraints.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayConstraints.kt
new file mode 100644
index 0000000000..f3573933e3
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayConstraints.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class RelayConstraints(
+ val location: Constraint<RelayItemId>,
+ val providers: Constraint<Providers>,
+ val ownership: Constraint<Ownership>,
+ val wireguardConstraints: WireguardConstraints,
+) {
+ companion object
+}
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
new file mode 100644
index 0000000000..a31a6f67df
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
@@ -0,0 +1,88 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+sealed interface RelayItem {
+ val id: RelayItemId
+ val name: String
+ val active: Boolean
+ val hasChildren: Boolean
+ val expanded: Boolean
+
+ @optics
+ data class CustomList(
+ override val id: CustomListId,
+ val customListName: CustomListName,
+ val locations: List<Location>,
+ override val expanded: Boolean,
+ ) : RelayItem {
+ override val name: String = customListName.value
+
+ override val active
+ get() = locations.any { location -> location.active }
+
+ override val hasChildren
+ get() = locations.isNotEmpty()
+
+ companion object
+ }
+
+ @optics
+ sealed interface Location : RelayItem {
+ override val id: GeoLocationId
+
+ @optics
+ data class Country(
+ override val id: GeoLocationId.Country,
+ override val name: String,
+ override val expanded: Boolean,
+ val cities: List<City>
+ ) : Location {
+ val relays = cities.flatMap { city -> city.relays }
+
+ override val active
+ get() = cities.any { city -> city.active }
+
+ override val hasChildren
+ get() = cities.isNotEmpty()
+
+ companion object
+ }
+
+ @optics
+ data class City(
+ override val id: GeoLocationId.City,
+ override val name: String,
+ override val expanded: Boolean,
+ val relays: List<Relay>
+ ) : Location {
+
+ override val active
+ get() = relays.any { relay -> relay.active }
+
+ override val hasChildren
+ get() = relays.isNotEmpty()
+
+ companion object
+ }
+
+ @optics
+ data class Relay(
+ override val id: GeoLocationId.Hostname,
+ val provider: Provider,
+ override val active: Boolean,
+ ) : Location {
+ override val name: String = id.hostname
+
+ override val hasChildren = false
+ override val expanded = false
+
+ companion object
+ }
+
+ companion object
+ }
+
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItemId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItemId.kt
new file mode 100644
index 0000000000..da59481269
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItemId.kt
@@ -0,0 +1,48 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import arrow.optics.optics
+import kotlinx.parcelize.Parcelize
+
+@optics
+sealed interface RelayItemId : Parcelable {
+ companion object
+}
+
+@optics
+@Parcelize
+@JvmInline
+value class CustomListId(val value: String) : RelayItemId, Parcelable {
+ companion object
+}
+
+@optics
+sealed interface GeoLocationId : RelayItemId {
+ @optics
+ @Parcelize
+ data class Country(val countryCode: String) : GeoLocationId {
+ companion object
+ }
+
+ @optics
+ @Parcelize
+ data class City(val countryCode: Country, val cityCode: String) : GeoLocationId {
+ companion object
+ }
+
+ @optics
+ @Parcelize
+ data class Hostname(val city: City, val hostname: String) : GeoLocationId {
+ companion object
+ }
+
+ val country: Country
+ get() =
+ when (this) {
+ is Country -> this
+ is City -> this.countryCode
+ is Hostname -> this.city.countryCode
+ }
+
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayList.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayList.kt
new file mode 100644
index 0000000000..39e43a713e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayList.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class RelayList(
+ val countries: List<RelayItem.Location.Country>,
+ val wireguardEndpointData: WireguardEndpointData
+)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayOverride.kt
index f738218ee7..3bd0a2f0a1 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayOverride.kt
@@ -1,12 +1,13 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
+import arrow.optics.optics
import java.net.InetAddress
-import kotlinx.parcelize.Parcelize
-@Parcelize
+@optics
data class RelayOverride(
val hostname: String,
val ipv4AddressIn: InetAddress?,
val ipv6AddressIn: InetAddress?
-) : Parcelable
+) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelaySettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelaySettings.kt
new file mode 100644
index 0000000000..ea40c980d0
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelaySettings.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class RelaySettings(val relayConstraints: RelayConstraints) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveDeviceError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveDeviceError.kt
new file mode 100644
index 0000000000..d00272ec63
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveDeviceError.kt
@@ -0,0 +1,9 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface RemoveDeviceError {
+ data object NotFound : RemoveDeviceError
+
+ data object RpcError : RemoveDeviceError
+
+ data class Unknown(val throwable: Throwable) : RemoveDeviceError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveSplitTunnelingAppError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveSplitTunnelingAppError.kt
new file mode 100644
index 0000000000..aa4dcfd8be
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveSplitTunnelingAppError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+interface RemoveSplitTunnelingAppError {
+ data class Unknown(val throwable: Throwable) : RemoveSplitTunnelingAppError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SelectedObfuscation.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SelectedObfuscation.kt
new file mode 100644
index 0000000000..1651d61db7
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SelectedObfuscation.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.lib.model
+
+enum class SelectedObfuscation {
+ Auto,
+ Off,
+ Udp2Tcp
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAllowLanError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAllowLanError.kt
new file mode 100644
index 0000000000..e30eba0d9e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAllowLanError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetAllowLanError {
+ data class Unknown(val throwable: Throwable) : SetAllowLanError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAutoConnectError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAutoConnectError.kt
new file mode 100644
index 0000000000..b2b3b74edf
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetAutoConnectError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetAutoConnectError {
+ data class Unknown(val throwable: Throwable) : SetAutoConnectError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDnsOptionsError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDnsOptionsError.kt
new file mode 100644
index 0000000000..8d72d8cebe
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetDnsOptionsError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetDnsOptionsError {
+ data class Unknown(val throwable: Throwable) : SetDnsOptionsError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetObfuscationOptionsError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetObfuscationOptionsError.kt
new file mode 100644
index 0000000000..d9c5acf650
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetObfuscationOptionsError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetObfuscationOptionsError {
+ data class Unknown(val throwable: Throwable) : SetObfuscationOptionsError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetRelayLocationError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetRelayLocationError.kt
new file mode 100644
index 0000000000..4606c46125
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetRelayLocationError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetRelayLocationError {
+ data class Unknown(val throwable: Throwable) : SetRelayLocationError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardConstraintsError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardConstraintsError.kt
new file mode 100644
index 0000000000..ccf8b4c8dc
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardConstraintsError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetWireguardConstraintsError {
+ data class Unknown(val throwable: Throwable) : SetWireguardConstraintsError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardMtuError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardMtuError.kt
new file mode 100644
index 0000000000..ca4f135fb1
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardMtuError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetWireguardMtuError {
+ data class Unknown(val throwable: Throwable) : SetWireguardMtuError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardQuantumResistantError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardQuantumResistantError.kt
new file mode 100644
index 0000000000..8121120c67
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetWireguardQuantumResistantError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetWireguardQuantumResistantError {
+ data class Unknown(val throwable: Throwable) : SetWireguardQuantumResistantError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
index 847b80cd70..c5191531be 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
@@ -1,16 +1,18 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
+import arrow.optics.optics
-@Parcelize
+@optics
data class Settings(
val relaySettings: RelaySettings,
val obfuscationSettings: ObfuscationSettings,
- val customLists: CustomListsSettings,
+ val customLists: List<CustomList>,
val allowLan: Boolean,
val autoConnect: Boolean,
val tunnelOptions: TunnelOptions,
- val relayOverrides: ArrayList<RelayOverride>,
+ val relayOverrides: List<RelayOverride>,
val showBetaReleases: Boolean,
-) : Parcelable
+ val splitTunnelSettings: SplitTunnelSettings
+) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SettingsPatchError.kt
index 5e3cb29911..1db1dc6f68 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SettingsPatchError.kt
@@ -1,10 +1,6 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-sealed class SettingsPatchError : Parcelable {
+sealed class SettingsPatchError {
// E.g hostname is number instead of String
data class InvalidOrMissingValue(val value: String) : SettingsPatchError()
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SplitTunnelSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SplitTunnelSettings.kt
new file mode 100644
index 0000000000..a937d53bae
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SplitTunnelSettings.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class SplitTunnelSettings(val enabled: Boolean, val excludedApps: Set<AppId>)
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TransportProtocol.kt
index 89fdedaba1..b25e3061be 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TransportProtocol.kt
@@ -1,4 +1,4 @@
-package net.mullvad.talpid.net
+package net.mullvad.mullvadvpn.lib.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt
index 9c45833eb2..d715f16766 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelEndpoint.kt
@@ -1,11 +1,7 @@
-package net.mullvad.talpid.net
+package net.mullvad.mullvadvpn.lib.model
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
data class TunnelEndpoint(
val endpoint: Endpoint,
val quantumResistant: Boolean,
val obfuscation: ObfuscationEndpoint?
-) : Parcelable
+)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelOptions.kt
new file mode 100644
index 0000000000..de1d760d30
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelOptions.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class TunnelOptions(val wireguard: WireguardTunnelOptions, val dnsOptions: DnsOptions) {
+ companion object
+}
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
new file mode 100644
index 0000000000..3fae41802a
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
@@ -0,0 +1,35 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed class TunnelState {
+ data class Disconnected(val location: GeoIpLocation? = null) : TunnelState()
+
+ data class Connecting(val endpoint: TunnelEndpoint?, val location: GeoIpLocation?) :
+ TunnelState()
+
+ data class Connected(val endpoint: TunnelEndpoint, val location: GeoIpLocation?) :
+ TunnelState()
+
+ data class Disconnecting(val actionAfterDisconnect: ActionAfterDisconnect) : TunnelState()
+
+ data class Error(val errorState: ErrorState) : TunnelState()
+
+ fun location(): GeoIpLocation? {
+ return when (this) {
+ is Connected -> location
+ is Connecting -> location
+ is Disconnecting -> null
+ is Disconnected -> location
+ is Error -> null
+ }
+ }
+
+ fun isSecured(): Boolean {
+ return when (this) {
+ is Connected,
+ is Connecting,
+ is Disconnecting, -> true
+ is Disconnected -> false
+ is Error -> this.errorState.isBlocking
+ }
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Udp2TcpObfuscationSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Udp2TcpObfuscationSettings.kt
new file mode 100644
index 0000000000..7447f7a4cf
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Udp2TcpObfuscationSettings.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class Udp2TcpObfuscationSettings(val port: Constraint<Port>)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateCustomListError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateCustomListError.kt
new file mode 100644
index 0000000000..ef49018dca
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateCustomListError.kt
@@ -0,0 +1,35 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface UpdateCustomListNameError {
+ companion object {
+ fun from(error: UpdateCustomListError): UpdateCustomListNameError =
+ when (error) {
+ is NameAlreadyExists -> error
+ is UnknownCustomListError -> error
+ }
+ }
+}
+
+sealed interface UpdateCustomListLocationsError {
+ companion object {
+ fun from(error: UpdateCustomListError): UpdateCustomListLocationsError =
+ when (error) {
+ is NameAlreadyExists -> error("Not supported error")
+ is UnknownCustomListError -> error
+ }
+ }
+}
+
+sealed interface UpdateCustomListError
+
+data class NameAlreadyExists(val name: String) : UpdateCustomListError, UpdateCustomListNameError
+
+data class UnknownCustomListError(val throwable: Throwable) :
+ UpdateCustomListError,
+ UpdateCustomListNameError,
+ UpdateCustomListLocationsError,
+ CreateCustomListError,
+ DeleteCustomListError
+
+data class GetCustomListError(val id: CustomListId) :
+ UpdateCustomListLocationsError, UpdateCustomListNameError
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WebsiteAuthToken.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WebsiteAuthToken.kt
new file mode 100644
index 0000000000..8ad9b85787
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WebsiteAuthToken.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+@JvmInline
+value class WebsiteAuthToken private constructor(val value: String) {
+ companion object {
+ fun fromString(value: String) = WebsiteAuthToken(value)
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardConstraints.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardConstraints.kt
new file mode 100644
index 0000000000..8affb81077
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardConstraints.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import arrow.optics.optics
+
+@optics
+data class WireguardConstraints(val port: Constraint<Port>) {
+ companion object
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardEndpointData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardEndpointData.kt
new file mode 100644
index 0000000000..8aff7d2895
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardEndpointData.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class WireguardEndpointData(val portRanges: List<PortRange>)
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
new file mode 100644
index 0000000000..573f08213e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/WireguardTunnelOptions.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class WireguardTunnelOptions(val mtu: Mtu?, val quantumResistant: QuantumResistantState)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/extensions/String.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/extensions/String.kt
new file mode 100644
index 0000000000..0df57eb057
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/extensions/String.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model.extensions
+
+fun String.startCase() =
+ split(" ").joinToString(" ") { word ->
+ word.replaceFirstChar { firstChar -> firstChar.uppercase() }
+ }
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountAndDevice.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountAndDevice.kt
deleted file mode 100644
index f5137ebbb7..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountAndDevice.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class AccountAndDevice(val account_token: String, val device: Device) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountCreationResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountCreationResult.kt
deleted file mode 100644
index 4bb4c61384..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountCreationResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class AccountCreationResult : Parcelable {
- @Parcelize data class Success(val accountToken: String) : AccountCreationResult()
-
- @Parcelize object Failure : AccountCreationResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt
deleted file mode 100644
index 6dda6b8352..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountData.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-data class AccountData(val expiry: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt
deleted file mode 100644
index f856ef8c89..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-import org.joda.time.DateTime
-
-sealed class AccountExpiry : Parcelable {
- @Parcelize data class Available(val expiryDateTime: DateTime) : AccountExpiry()
-
- @Parcelize data object Missing : AccountExpiry()
-
- fun date(): DateTime? {
- return (this as? Available)?.expiryDateTime
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountHistory.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountHistory.kt
deleted file mode 100644
index f003ee316b..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountHistory.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class AccountHistory : Parcelable {
- @Parcelize data class Available(val accountToken: String) : AccountHistory()
-
- @Parcelize object Missing : AccountHistory()
-
- fun accountToken() = (this as? Available)?.accountToken
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountToken.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountToken.kt
deleted file mode 100644
index 2aeca352d0..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountToken.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-@JvmInline value class AccountToken(val value: String)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AppVersionInfo.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AppVersionInfo.kt
deleted file mode 100644
index bbe99ce656..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AppVersionInfo.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class AppVersionInfo(val supported: Boolean, val suggestedUpgrade: String?) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Constraint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Constraint.kt
deleted file mode 100644
index d9ca22b164..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Constraint.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class Constraint<T> : Parcelable {
- @Parcelize @Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY") class Any<T> : Constraint<T>()
-
- @Parcelize data class Only<T : Parcelable>(val value: T) : Constraint<T>()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CreateCustomListResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CreateCustomListResult.kt
deleted file mode 100644
index 73eaa209c8..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CreateCustomListResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class CreateCustomListResult : Parcelable {
- @Parcelize data class Ok(val id: String) : CreateCustomListResult()
-
- @Parcelize data class Error(val error: CustomListsError) : CreateCustomListResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomDnsOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomDnsOptions.kt
deleted file mode 100644
index bbf029dd4d..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomDnsOptions.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import java.net.InetAddress
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class CustomDnsOptions(val addresses: ArrayList<InetAddress>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomList.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomList.kt
deleted file mode 100644
index cdfa1b9687..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomList.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class CustomList(
- val id: String,
- val name: String,
- val locations: ArrayList<GeographicLocationConstraint>
-) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsError.kt
deleted file mode 100644
index 83806af4f7..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsError.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-enum class CustomListsError {
- CustomListExists,
- OtherError
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsSettings.kt
deleted file mode 100644
index 8a8c03ef05..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListsSettings.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class CustomListsSettings(val customLists: ArrayList<CustomList>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomTunnelEndpoint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomTunnelEndpoint.kt
deleted file mode 100644
index 72276c65e4..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomTunnelEndpoint.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-class CustomTunnelEndpoint
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Device.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Device.kt
deleted file mode 100644
index 0f0a55d05d..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Device.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class Device(
- val id: String,
- private val name: String,
- val pubkey: ByteArray,
- val created: String
-) : Parcelable {
- // Generated by Android Studio
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as Device
-
- if (id != other.id) return false
- if (name != other.name) return false
- return pubkey.contentEquals(other.pubkey)
- }
-
- // Generated by Android Studio
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + name.hashCode()
- result = 31 * result + pubkey.contentHashCode()
- return result
- }
-
- fun displayName(): String = name.capitalizeFirstCharOfEachWord()
-}
-
-private fun String.capitalizeFirstCharOfEachWord(): String {
- return split(" ")
- .joinToString(" ") { word -> word.replaceFirstChar { firstChar -> firstChar.uppercase() } }
- .trimEnd()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEvent.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEvent.kt
deleted file mode 100644
index 741108612d..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEvent.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class DeviceEvent(val cause: DeviceEventCause, val newState: DeviceState) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEventCause.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEventCause.kt
deleted file mode 100644
index b4c1d21761..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceEventCause.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class DeviceEventCause : Parcelable {
- LoggedIn,
- LoggedOut,
- Revoked,
- Updated,
- RotatedKey
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceList.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceList.kt
deleted file mode 100644
index afe5982ed5..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceList.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-sealed class DeviceList {
- object Unavailable : DeviceList()
-
- data class Available(val devices: List<Device>) : DeviceList()
-
- object Error : DeviceList()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceListEvent.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceListEvent.kt
deleted file mode 100644
index 7a2883617b..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceListEvent.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class DeviceListEvent : Parcelable {
- @Parcelize
- data class Available(val accountToken: String, val devices: List<Device>) : DeviceListEvent()
-
- @Parcelize object Error : DeviceListEvent()
-
- fun isAvailable(): Boolean {
- return (this is Available)
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DevicePort.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DevicePort.kt
deleted file mode 100644
index e43eae3e6b..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DevicePort.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class DevicePort(val id: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt
deleted file mode 100644
index fb34c9e645..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/DeviceState.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class DeviceState : Parcelable {
- @Parcelize object Initial : DeviceState()
-
- @Parcelize object Unknown : DeviceState()
-
- @Parcelize data class LoggedIn(val accountAndDevice: AccountAndDevice) : DeviceState()
-
- @Parcelize object LoggedOut : DeviceState()
-
- @Parcelize object Revoked : DeviceState()
-
- fun isUnknown(): Boolean {
- return this is Unknown
- }
-
- fun deviceName(): String? {
- return (this as? LoggedIn)?.accountAndDevice?.device?.displayName()
- }
-
- fun token(): String? {
- return (this as? LoggedIn)?.accountAndDevice?.account_token
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt
deleted file mode 100644
index 386257a72a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GeographicLocationConstraint.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class GeographicLocationConstraint : Parcelable {
- abstract val location: GeoIpLocation
-
- @Parcelize
- data class Country(val countryCode: String) : GeographicLocationConstraint() {
- override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, null, 0.0, 0.0, null)
- }
-
- @Parcelize
- data class City(val countryCode: String, val cityCode: String) :
- GeographicLocationConstraint() {
- override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, cityCode, 0.0, 0.0, null)
- }
-
- @Parcelize
- data class Hostname(val countryCode: String, val cityCode: String, val hostname: String) :
- GeographicLocationConstraint() {
- override val location: GeoIpLocation
- get() = GeoIpLocation(null, null, countryCode, cityCode, 0.0, 0.0, hostname)
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GetAccountDataResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GetAccountDataResult.kt
deleted file mode 100644
index 2e94266e2a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/GetAccountDataResult.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-sealed class GetAccountDataResult {
- class Ok(val accountData: AccountData) : GetAccountDataResult()
-
- object InvalidAccount : GetAccountDataResult()
-
- object RpcError : GetAccountDataResult()
-
- object OtherError : GetAccountDataResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt
deleted file mode 100644
index 0c9d331e3b..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LocationConstraint.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class LocationConstraint : Parcelable {
- @Parcelize
- data class Location(val location: GeographicLocationConstraint) : LocationConstraint()
-
- @Parcelize data class CustomList(val listId: String) : LocationConstraint()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginResult.kt
deleted file mode 100644
index 29fb68203d..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginResult.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class LoginResult : Parcelable {
- Ok,
- InvalidAccount,
- MaxDevicesReached,
- RpcError,
- OtherError
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Ownership.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Ownership.kt
deleted file mode 100644
index 43037be676..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Ownership.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class Ownership : Parcelable {
- MullvadOwned,
- Rented
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchase.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchase.kt
deleted file mode 100644
index 8ae46a07a9..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchase.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class PlayPurchase(val productId: String, val purchaseToken: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitError.kt
deleted file mode 100644
index 39aebabbe2..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitError.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class PlayPurchaseInitError : Parcelable {
- // TODO: Add more errors here.
- OtherError
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitResult.kt
deleted file mode 100644
index 41407474af..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseInitResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class PlayPurchaseInitResult : Parcelable {
- @Parcelize data class Ok(val obfuscatedId: String) : PlayPurchaseInitResult()
-
- @Parcelize data class Error(val error: PlayPurchaseInitError) : PlayPurchaseInitResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyError.kt
deleted file mode 100644
index b0434c22f9..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyError.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class PlayPurchaseVerifyError : Parcelable {
- // TODO: Add more errors here.
- OtherError
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyResult.kt
deleted file mode 100644
index 7c5ee4d953..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PlayPurchaseVerifyResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class PlayPurchaseVerifyResult : Parcelable {
- @Parcelize data object Ok : PlayPurchaseVerifyResult()
-
- @Parcelize data class Error(val error: PlayPurchaseVerifyError) : PlayPurchaseVerifyResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Port.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Port.kt
deleted file mode 100644
index 52f495a7a7..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Port.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class Port(val value: Int) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PortRange.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PortRange.kt
deleted file mode 100644
index 376f5ef7a4..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PortRange.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class PortRange(val from: Int, val to: Int) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Providers.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Providers.kt
deleted file mode 100644
index d3c6aacba9..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Providers.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Suppress("ensure value classes property is named value")
-@JvmInline
-@Parcelize
-value class Providers(val providers: HashSet<String>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PublicKey.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PublicKey.kt
deleted file mode 100644
index 169b6c3856..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/PublicKey.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class PublicKey(val key: ByteArray, val dateCreated: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/QuantumResistantState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/QuantumResistantState.kt
deleted file mode 100644
index a19267388a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/QuantumResistantState.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class QuantumResistantState : Parcelable {
- Auto,
- On,
- Off
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt
deleted file mode 100644
index 461648209c..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class Relay(
- val hostname: String,
- val active: Boolean,
- val owned: Boolean,
- val provider: String,
- val endpointData: RelayEndpointData
-) : Parcelable {
- val isWireguardRelay
- get() = endpointData is RelayEndpointData.Wireguard
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayConstraints.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayConstraints.kt
deleted file mode 100644
index 031b09bace..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayConstraints.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class RelayConstraints(
- val location: Constraint<LocationConstraint>,
- val providers: Constraint<Providers>,
- val ownership: Constraint<Ownership>,
- val wireguardConstraints: WireguardConstraints,
-) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt
deleted file mode 100644
index 86b3f0fa35..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class RelayEndpointData : Parcelable {
- @Parcelize object Openvpn : RelayEndpointData()
-
- @Parcelize object Bridge : RelayEndpointData()
-
- @Parcelize
- data class Wireguard(val wireguardRelayEndpointData: WireguardRelayEndpointData) :
- RelayEndpointData()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayList.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayList.kt
deleted file mode 100644
index 60d8b6dd35..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayList.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class RelayList(
- val countries: ArrayList<RelayListCountry>,
- val wireguardEndpointData: WireguardEndpointData
-) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCity.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCity.kt
deleted file mode 100644
index 2376609ced..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCity.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class RelayListCity(val name: String, val code: String, val relays: ArrayList<Relay>) :
- Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCountry.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCountry.kt
deleted file mode 100644
index d6d4b8ec6a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayListCountry.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class RelayListCountry(
- val name: String,
- val code: String,
- val cities: ArrayList<RelayListCity>
-) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelaySettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelaySettings.kt
deleted file mode 100644
index 642046f1b8..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelaySettings.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class RelaySettings : Parcelable {
- @Parcelize data object CustomTunnelEndpoint : RelaySettings()
-
- @Parcelize data class Normal(val relayConstraints: RelayConstraints) : RelaySettings()
-
- fun relayConstraints(): RelayConstraints? = (this as? Normal)?.relayConstraints
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceEvent.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceEvent.kt
deleted file mode 100644
index cc6e7db2bb..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceEvent.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class RemoveDeviceEvent(val accountToken: String, val newDevices: ArrayList<Device>) :
- Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceResult.kt
deleted file mode 100644
index 67bf165a37..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RemoveDeviceResult.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class RemoveDeviceResult : Parcelable {
- Ok,
- NotFound,
- RpcError,
- OtherError
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SelectedObfuscation.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SelectedObfuscation.kt
deleted file mode 100644
index 8124bcc6a6..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SelectedObfuscation.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class SelectedObfuscation : Parcelable {
- Auto,
- Off,
- Udp2Tcp
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ServiceResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ServiceResult.kt
deleted file mode 100644
index e597797e5a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/ServiceResult.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.IBinder
-
-data class ServiceResult(val binder: IBinder?) {
- enum class ConnectionState {
- CONNECTED,
- DISCONNECTED
- }
-
- val connectionState: ConnectionState
- get() {
- return if (binder == null) {
- ConnectionState.DISCONNECTED
- } else {
- ConnectionState.CONNECTED
- }
- }
-
- companion object {
- val NOT_CONNECTED = ServiceResult(null)
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelOptions.kt
deleted file mode 100644
index 108fd32e04..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelOptions.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class TunnelOptions(val wireguard: WireguardTunnelOptions, val dnsOptions: DnsOptions) :
- Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt
deleted file mode 100644
index 4ab925d014..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-import net.mullvad.talpid.net.TunnelEndpoint
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-import net.mullvad.talpid.tunnel.ErrorState
-
-sealed class TunnelState : Parcelable {
- @Parcelize
- data class Disconnected(val location: GeoIpLocation? = null) : TunnelState(), Parcelable
-
- @Parcelize
- class Connecting(val endpoint: TunnelEndpoint?, val location: GeoIpLocation?) :
- TunnelState(), Parcelable
-
- @Parcelize
- class Connected(val endpoint: TunnelEndpoint, val location: GeoIpLocation?) :
- TunnelState(), Parcelable
-
- @Parcelize
- class Disconnecting(val actionAfterDisconnect: ActionAfterDisconnect) :
- TunnelState(), Parcelable
-
- @Parcelize class Error(val errorState: ErrorState) : TunnelState(), Parcelable
-
- fun location(): GeoIpLocation? {
- return when (this) {
- is Connected -> location
- is Connecting -> location
- is Disconnecting -> null
- is Disconnected -> location
- is Error -> null
- }
- }
-
- fun isSecured(): Boolean {
- return when (this) {
- is Connected,
- is Connecting,
- is Disconnecting, -> true
- is Disconnected -> false
- is Error -> this.errorState.isBlocking
- }
- }
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Udp2TcpObfuscationSettings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Udp2TcpObfuscationSettings.kt
deleted file mode 100644
index f01bb35c6f..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Udp2TcpObfuscationSettings.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class Udp2TcpObfuscationSettings(val port: Constraint<Int>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/UpdateCustomListResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/UpdateCustomListResult.kt
deleted file mode 100644
index ebfe9e8cd6..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/UpdateCustomListResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class UpdateCustomListResult : Parcelable {
- @Parcelize data object Ok : UpdateCustomListResult()
-
- @Parcelize data class Error(val error: CustomListsError) : UpdateCustomListResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmission.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmission.kt
deleted file mode 100644
index efe05e2f5c..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmission.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class VoucherSubmission(val timeAdded: Long, val newExpiry: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionError.kt
deleted file mode 100644
index 1cf778400a..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionError.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class VoucherSubmissionError : Parcelable {
- InvalidVoucher,
- VoucherAlreadyUsed,
- RpcError,
- OtherError,
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionResult.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionResult.kt
deleted file mode 100644
index 4163b782d4..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/VoucherSubmissionResult.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-sealed class VoucherSubmissionResult : Parcelable {
- @Parcelize data class Ok(val submission: VoucherSubmission) : VoucherSubmissionResult()
-
- @Parcelize data class Error(val error: VoucherSubmissionError) : VoucherSubmissionResult()
-}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardConstraints.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardConstraints.kt
deleted file mode 100644
index 1725b01f0f..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardConstraints.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class WireguardConstraints(val port: Constraint<Port>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt
deleted file mode 100644
index 0a21221bb0..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class WireguardEndpointData(val portRanges: ArrayList<PortRange>) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt
deleted file mode 100644
index 4a1930dd43..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize object WireguardRelayEndpointData : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardTunnelOptions.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardTunnelOptions.kt
deleted file mode 100644
index f4a869a4ea..0000000000
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardTunnelOptions.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.mullvadvpn.model
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class WireguardTunnelOptions(val mtu: Int?, val quantumResistant: QuantumResistantState) :
- Parcelable
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatLongTest.kt
index b8608ca55c..8abef5d9b3 100644
--- a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatLongTest.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.sqrt
import org.junit.jupiter.api.Assertions.assertEquals
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatitudeTest.kt
index c883f20bfc..214afef127 100644
--- a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatitudeTest.kt
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LatitudeTest.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.absoluteValue
import kotlin.test.assertEquals
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LongitudeTest.kt
index 69d3445417..88017cdcea 100644
--- a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LongitudeTest.kt
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/LongitudeTest.kt
@@ -1,4 +1,4 @@
-package net.mullvad.mullvadvpn.model
+package net.mullvad.mullvadvpn.lib.model
import kotlin.math.absoluteValue
import kotlin.test.assertEquals
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index 38a839bed3..f9acacb5d9 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -80,19 +80,18 @@
<string name="vpn_settings_not_found">There is no VPN settings on your device</string>
<string name="auto_connect_carousel_first_slide_top_text">The Auto-connect and Lockdown mode settings can be found in the Android system settings, follow this guide to enable one or both.</string>
<string name="auto_connect_carousel_first_slide_bottom_text">
- <![CDATA[1. After clicking on the <b>Go to VPN settings</b> button below, click on the cogwheel next to the <b>Mullvad VPN</b> name.]]>
+ <![CDATA[1. After clicking on the <b>Go to VPN settings</b> button below, click on the cogwheel next to the <b>Mullvad VPN</b> name.]]>
</string>
<string name="auto_connect_carousel_second_slide_top_text">Auto-connect is called Always-on VPN in the Android system settings and it makes sure you are constantly connected to the VPN tunnel and auto connects after restart.</string>
<string name="auto_connect_carousel_second_slide_bottom_text">
- <![CDATA[2. To enable Auto-connect, click on the toggle next to <b>Always-on VPN</b>.]]>
- </string>
+ <![CDATA[2. To enable Auto-connect, click on the toggle next to <b>Always-on VPN</b>.]]>
+ </string>
<string name="auto_connect_carousel_third_slide_top_text">
<![CDATA[The Lockdown mode blocks all internet access if the VPN tunnel is manually disconnected. <br/><b>Warning: This setting blocks split apps and the Local Network Sharing feature</b>.]]>
</string>
<string name="auto_connect_carousel_third_slide_bottom_text">
<![CDATA[3. To enable Lockdown mode, click on the toggle next to <b>Block connections without VPN</b>.]]>
</string>
-
<string name="auto_connect_footer">Automatically connect to a server when the app launches.</string>
<string name="wireguard_mtu">WireGuard MTU</string>
<string name="wireguard_mtu_footer">Set WireGuard MTU value. Valid range: %1$d - %2$d.</string>
@@ -281,7 +280,9 @@
<string name="loading_verifying">Verifying purchase...</string>
<string name="copied_logs_to_clipboard">Copied logs to clipboard</string>
<string name="auto_connect_legacy">Auto-connect (legacy)</string>
- <string name="auto_connect_footer_legacy"><![CDATA[Please use the <b>Always-on</b> system setting instead by following the guide in <b>%s</b> above.]]></string>
+ <string name="auto_connect_footer_legacy">
+ <![CDATA[Please use the <b>Always-on</b> system setting instead by following the guide in <b>%s</b> above.]]>
+ </string>
<string name="custom_lists">Custom lists</string>
<string name="all_locations">All locations</string>
<string name="edit_lists">Edit lists</string>
@@ -295,9 +296,7 @@
<string name="list_name">List name</string>
<string name="locations">Locations</string>
<string name="edit_locations">Edit locations</string>
- <string name="delete_custom_list_confirmation_description">
- Delete \"%s\"?
- </string>
+ <string name="delete_custom_list_confirmation_description">Delete \"%s\"?</string>
<string name="custom_list_error_list_exists">Name is already taken.</string>
<string name="update_list_name">Update list name</string>
<string name="no_custom_lists_available">No custom lists available</string>
@@ -344,4 +343,5 @@
<string name="settings_patch_error_recursion_limit">Recursion limit</string>
<string name="settings_patch_success">Import successful, overrides active</string>
<string name="overrides_cleared">Overrides cleared</string>
+ <string name="unsecured_vpn_permission_error">Unsecured (No VPN permission)</string>
</resources>
diff --git a/android/lib/shared/build.gradle.kts b/android/lib/shared/build.gradle.kts
new file mode 100644
index 0000000000..88b5cfb3c9
--- /dev/null
+++ b/android/lib/shared/build.gradle.kts
@@ -0,0 +1,47 @@
+plugins {
+ id(Dependencies.Plugin.androidLibraryId)
+ id(Dependencies.Plugin.kotlinAndroidId)
+ id(Dependencies.Plugin.kotlinParcelizeId)
+ id(Dependencies.Plugin.junit5) version Versions.Plugin.junit5
+}
+
+android {
+ namespace = "net.mullvad.mullvadvpn.lib.shared"
+ compileSdk = Versions.Android.compileSdkVersion
+
+ defaultConfig { minSdk = Versions.Android.minSdkVersion }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions { jvmTarget = Versions.jvmTarget }
+
+ lint {
+ lintConfig = file("${rootProject.projectDir}/config/lint.xml")
+ abortOnError = true
+ warningsAsErrors = true
+ }
+ buildFeatures { buildConfig = true }
+}
+
+dependencies {
+ implementation(project(Dependencies.Mullvad.commonLib))
+ implementation(project(Dependencies.Mullvad.daemonGrpc))
+ implementation(project(Dependencies.Mullvad.modelLib))
+
+ implementation(Dependencies.Arrow.core)
+ implementation(Dependencies.Kotlin.stdlib)
+ implementation(Dependencies.KotlinX.coroutinesAndroid)
+ implementation(Dependencies.jodaTime)
+
+ testImplementation(Dependencies.Kotlin.test)
+ testImplementation(Dependencies.KotlinX.coroutinesTest)
+ testImplementation(Dependencies.MockK.core)
+ testImplementation(Dependencies.junitApi)
+ testImplementation(Dependencies.junitParams)
+ testImplementation(Dependencies.turbine)
+ testImplementation(project(Dependencies.Mullvad.commonTestLib))
+ testRuntimeOnly(Dependencies.junitEngine)
+}
diff --git a/android/lib/shared/src/main/AndroidManifest.xml b/android/lib/shared/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..cc947c5679
--- /dev/null
+++ b/android/lib/shared/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest />
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt
new file mode 100644
index 0000000000..432d113fba
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt
@@ -0,0 +1,84 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import arrow.core.Either
+import arrow.core.raise.nullable
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.CreateAccountError
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.LoginAccountError
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
+import org.joda.time.DateTime
+
+class AccountRepository(
+ private val managementService: ManagementService,
+ private val deviceRepository: DeviceRepository,
+ val scope: CoroutineScope
+) {
+
+ private val _mutableAccountDataCache: MutableSharedFlow<AccountData> = MutableSharedFlow()
+
+ private val _isNewAccount: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ val isNewAccount: StateFlow<Boolean> = _isNewAccount
+ val accountData: StateFlow<AccountData?> =
+ merge(
+ managementService.deviceState.filterNotNull().map { deviceState ->
+ when (deviceState) {
+ is DeviceState.LoggedIn -> {
+ managementService.getAccountData(deviceState.accountToken).getOrNull()
+ }
+ DeviceState.LoggedOut,
+ DeviceState.Revoked -> null
+ }
+ },
+ _mutableAccountDataCache
+ )
+ .distinctUntilChanged()
+ .stateIn(scope = scope, SharingStarted.Eagerly, null)
+
+ suspend fun createAccount(): Either<CreateAccountError, AccountToken> =
+ managementService.createAccount().onRight { _isNewAccount.update { true } }
+
+ suspend fun login(accountToken: AccountToken): Either<LoginAccountError, Unit> =
+ managementService.loginAccount(accountToken)
+
+ suspend fun logout() {
+ managementService.logoutAccount()
+ _isNewAccount.update { false }
+ }
+
+ suspend fun fetchAccountHistory(): AccountToken? =
+ managementService.getAccountHistory().getOrNull()
+
+ suspend fun clearAccountHistory() = managementService.clearAccountHistory()
+
+ suspend fun getAccountData(): AccountData? = nullable {
+ val deviceState = ensureNotNull(deviceRepository.deviceState.value as? DeviceState.LoggedIn)
+
+ val accountData =
+ managementService.getAccountData(deviceState.accountToken).getOrNull().bind()
+
+ // Update stateflow cache
+ _mutableAccountDataCache.emit(accountData)
+ accountData
+ }
+
+ suspend fun getWebsiteAuthToken(): WebsiteAuthToken? =
+ managementService.getWebsiteAuthToken().getOrNull()
+
+ internal suspend fun onVoucherRedeemed(newExpiry: DateTime) {
+ accountData.value?.copy(expiryDate = newExpiry)?.let { _mutableAccountDataCache.emit(it) }
+ }
+}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt
new file mode 100644
index 0000000000..6ea373e426
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxy.kt
@@ -0,0 +1,26 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import arrow.core.Either
+import arrow.core.raise.either
+import arrow.core.raise.ensure
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.ConnectError
+
+class ConnectionProxy(
+ private val managementService: ManagementService,
+ private val vpnPermissionRepository: VpnPermissionRepository
+) {
+ val tunnelState = managementService.tunnelState
+
+ suspend fun connect(): Either<ConnectError, Boolean> = either {
+ ensure(vpnPermissionRepository.hasVpnPermission()) { ConnectError.NoVpnPermission }
+ managementService.connect().bind()
+ }
+
+ suspend fun connectWithoutPermissionCheck(): Either<ConnectError, Boolean> =
+ managementService.connect()
+
+ suspend fun disconnect() = managementService.disconnect()
+
+ suspend fun reconnect() = managementService.reconnect()
+}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/DeviceRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/DeviceRepository.kt
new file mode 100644
index 0000000000..b1b8f4fa41
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/DeviceRepository.kt
@@ -0,0 +1,36 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import arrow.core.Either
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.DeleteDeviceError
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceId
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.GetDeviceListError
+
+class DeviceRepository(
+ private val managementService: ManagementService,
+ dispatcher: CoroutineDispatcher = Dispatchers.IO
+) {
+ val deviceState: StateFlow<DeviceState?> =
+ managementService.deviceState.stateIn(
+ CoroutineScope(dispatcher),
+ SharingStarted.Eagerly,
+ null
+ )
+
+ suspend fun removeDevice(
+ accountToken: AccountToken,
+ deviceId: DeviceId
+ ): Either<DeleteDeviceError, Unit> = managementService.removeDevice(accountToken, deviceId)
+
+ suspend fun deviceList(accountToken: AccountToken): Either<GetDeviceListError, List<Device>> =
+ managementService.getDeviceList(accountToken)
+}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VoucherRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VoucherRepository.kt
new file mode 100644
index 0000000000..a5783a832e
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VoucherRepository.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+
+class VoucherRepository(
+ private val managementService: ManagementService,
+ private val accountRepository: AccountRepository
+) {
+ suspend fun submitVoucher(voucher: String) =
+ managementService.submitVoucher(voucher).onRight {
+ accountRepository.onVoucherRedeemed(it.newExpiryDate)
+ }
+}
diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt
new file mode 100644
index 0000000000..b97c60316c
--- /dev/null
+++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/VpnPermissionRepository.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import android.content.Context
+import android.net.VpnService
+import net.mullvad.mullvadvpn.lib.common.util.getAlwaysOnVpnAppName
+
+class VpnPermissionRepository(private val applicationContext: Context) {
+ fun hasVpnPermission(): Boolean = VpnService.prepare(applicationContext) == null
+
+ fun getAlwaysOnVpnAppName() = applicationContext.getAlwaysOnVpnAppName()
+}
diff --git a/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt b/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt
new file mode 100644
index 0000000000..74ab4f6b64
--- /dev/null
+++ b/android/lib/shared/src/test/kotlin/net/mullvad/mullvadvpn/lib/shared/ConnectionProxyTest.kt
@@ -0,0 +1,54 @@
+package net.mullvad.mullvadvpn.lib.shared
+
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Test
+
+class ConnectionProxyTest {
+
+ private val mockManagementService: ManagementService = mockk(relaxed = true)
+ private val mockVpnPermissionRepository: VpnPermissionRepository = mockk()
+
+ private val connectionProxy: ConnectionProxy =
+ ConnectionProxy(
+ managementService = mockManagementService,
+ vpnPermissionRepository = mockVpnPermissionRepository
+ )
+
+ @Test
+ fun `connect with vpn permission allowed should call managementService connect`() = runTest {
+ every { mockVpnPermissionRepository.hasVpnPermission() } returns true
+ connectionProxy.connect()
+ coVerify(exactly = 1) { mockManagementService.connect() }
+ }
+
+ @Test
+ fun `connect with vpn permission not allowed should not call managementService connect`() =
+ runTest {
+ every { mockVpnPermissionRepository.hasVpnPermission() } returns false
+ connectionProxy.connect()
+ coVerify(exactly = 0) { mockManagementService.connect() }
+ }
+
+ @Test
+ fun `disconnect should call managementService disconnect`() = runTest {
+ connectionProxy.disconnect()
+ coVerify(exactly = 1) { mockManagementService.disconnect() }
+ }
+
+ @Test
+ fun `reconnect should call managementService reconnect`() = runTest {
+ connectionProxy.reconnect()
+ coVerify(exactly = 1) { mockManagementService.reconnect() }
+ }
+
+ @AfterEach
+ fun tearDown() {
+ unmockkAll()
+ }
+}
diff --git a/android/lib/talpid/build.gradle.kts b/android/lib/talpid/build.gradle.kts
index ac760a860e..00409f9482 100644
--- a/android/lib/talpid/build.gradle.kts
+++ b/android/lib/talpid/build.gradle.kts
@@ -25,6 +25,9 @@ android {
}
dependencies {
+ implementation(project(Dependencies.Mullvad.modelLib))
+
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.KotlinX.coroutinesAndroid)
+ implementation(Dependencies.AndroidX.lifecycleService)
}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt
index cdc16567e1..905f59f313 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt
@@ -7,7 +7,6 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlin.properties.Delegates.observable
-import net.mullvad.talpid.util.EventNotifier
class ConnectivityListener {
private val availableNetworks = HashSet<Network>()
@@ -21,22 +20,19 @@ class ConnectivityListener {
override fun onLost(network: Network) {
availableNetworks.remove(network)
- isConnected = !availableNetworks.isEmpty()
+ isConnected = availableNetworks.isNotEmpty()
}
}
private lateinit var connectivityManager: ConnectivityManager
- val connectivityNotifier = EventNotifier(false)
-
+ // Used by JNI
var isConnected by
observable(false) { _, oldValue, newValue ->
if (newValue != oldValue) {
if (senderAddress != 0L) {
notifyConnectivityChange(newValue, senderAddress)
}
-
- connectivityNotifier.notify(newValue)
}
}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/LifecycleVpnService.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/LifecycleVpnService.kt
new file mode 100644
index 0000000000..efb29c31c6
--- /dev/null
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/LifecycleVpnService.kt
@@ -0,0 +1,56 @@
+package net.mullvad.talpid
+
+import android.content.Intent
+import android.net.VpnService
+import android.os.IBinder
+import androidx.annotation.CallSuper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ServiceLifecycleDispatcher
+
+/**
+ * A VpnService that is also a [LifecycleOwner]. See source:
+ * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt?q=file:androidx%2Flifecycle%2FLifecycleService.kt%20class:androidx.lifecycle.LifecycleService
+ */
+open class LifecycleVpnService : VpnService(), LifecycleOwner {
+
+ private val dispatcher = ServiceLifecycleDispatcher(this)
+
+ @CallSuper
+ override fun onCreate() {
+ dispatcher.onServicePreSuperOnCreate()
+ super.onCreate()
+ }
+
+ @CallSuper
+ override fun onBind(intent: Intent?): IBinder? {
+ dispatcher.onServicePreSuperOnBind()
+ return super.onBind(intent)
+ }
+
+ @Deprecated("Deprecated in Java")
+ @Suppress("DEPRECATION")
+ @CallSuper
+ override fun onStart(intent: Intent?, startId: Int) {
+ dispatcher.onServicePreSuperOnStart()
+ super.onStart(intent, startId)
+ }
+
+ // this method is added only to annotate it with @CallSuper.
+ // In usual Service, super.onStartCommand is no-op, but in LifecycleService
+ // it results in dispatcher.onServicePreSuperOnStart() call, because
+ // super.onStartCommand calls onStart().
+ @CallSuper
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ @CallSuper
+ override fun onDestroy() {
+ dispatcher.onServicePreSuperOnDestroy()
+ super.onDestroy()
+ }
+
+ override val lifecycle: Lifecycle
+ get() = dispatcher.lifecycle
+}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
index 76abde2a01..e89c841d25 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
@@ -1,16 +1,17 @@
package net.mullvad.talpid
-import android.net.VpnService
import android.os.ParcelFileDescriptor
import android.util.Log
+import androidx.annotation.CallSuper
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import kotlin.properties.Delegates.observable
-import net.mullvad.talpid.tun_provider.TunConfig
+import net.mullvad.talpid.model.CreateTunResult
+import net.mullvad.talpid.model.TunConfig
import net.mullvad.talpid.util.TalpidSdkUtils.setMeteredIfSupported
-open class TalpidVpnService : VpnService() {
+open class TalpidVpnService : LifecycleVpnService() {
private var activeTunStatus by
observable<CreateTunResult?>(null) { _, oldTunStatus, _ ->
val oldTunFd =
@@ -29,17 +30,19 @@ open class TalpidVpnService : VpnService() {
get() = activeTunStatus?.isOpen ?: false
private var currentTunConfig = defaultTunConfig()
- private var tunIsStale = false
-
- protected var disallowedApps: List<String>? = null
+ // Used by JNI
val connectivityListener = ConnectivityListener()
+ @CallSuper
override fun onCreate() {
+ super.onCreate()
connectivityListener.register(this)
}
+ @CallSuper
override fun onDestroy() {
+ super.onDestroy()
connectivityListener.unregister()
}
@@ -47,14 +50,13 @@ open class TalpidVpnService : VpnService() {
synchronized(this) {
val tunStatus = activeTunStatus
- if (config == currentTunConfig && tunIsOpen && !tunIsStale) {
+ if (config == currentTunConfig && tunIsOpen) {
return tunStatus!!
} else {
val newTunStatus = createTun(config)
currentTunConfig = config
activeTunStatus = newTunStatus
- tunIsStale = false
return newTunStatus
}
@@ -78,17 +80,13 @@ open class TalpidVpnService : VpnService() {
synchronized(this) { activeTunStatus = null }
}
- fun markTunAsStale() {
- synchronized(this) { tunIsStale = true }
- }
-
private fun createTun(config: TunConfig): CreateTunResult {
if (prepare(this) != null) {
// VPN permission wasn't granted
return CreateTunResult.PermissionDenied
}
- var invalidDnsServerAddresses = ArrayList<InetAddress>()
+ val invalidDnsServerAddresses = ArrayList<InetAddress>()
val builder =
Builder().apply {
@@ -120,11 +118,7 @@ open class TalpidVpnService : VpnService() {
addRoute(route.address, route.prefixLength.toInt())
}
- disallowedApps?.let { apps ->
- for (app in apps) {
- addDisallowedApplication(app)
- }
- }
+ config.excludedPackages.forEach { app -> addDisallowedApplication(app) }
setMtu(config.mtu)
setBlocking(false)
setMeteredIfSupported(false)
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt
index 33f62026d6..089112e3ab 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/CreateTunResult.kt
@@ -1,4 +1,4 @@
-package net.mullvad.talpid
+package net.mullvad.talpid.model
import java.net.InetAddress
@@ -17,7 +17,7 @@ sealed class CreateTunResult {
get() = true
}
- object PermissionDenied : CreateTunResult()
+ data object PermissionDenied : CreateTunResult()
- object TunnelDeviceError : CreateTunResult()
+ data object TunnelDeviceError : CreateTunResult()
}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/InetNetwork.kt
index a8490b48bf..a9c257c3e4 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/InetNetwork.kt
@@ -1,4 +1,4 @@
-package net.mullvad.talpid.tun_provider
+package net.mullvad.talpid.model
import java.net.Inet6Address
import java.net.InetAddress
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/TunConfig.kt
index 7efd3f7763..955a6f4454 100644
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt
+++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/TunConfig.kt
@@ -1,4 +1,4 @@
-package net.mullvad.talpid.tun_provider
+package net.mullvad.talpid.model
import java.net.InetAddress
@@ -6,5 +6,6 @@ data class TunConfig(
val addresses: ArrayList<InetAddress>,
val dnsServers: ArrayList<InetAddress>,
val routes: ArrayList<InetNetwork>,
+ val excludedPackages: ArrayList<String>,
val mtu: Int
)
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt
deleted file mode 100644
index 8937bd0122..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.talpid.net
-
-import android.os.Parcelable
-import java.net.InetSocketAddress
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class Endpoint(val address: InetSocketAddress, val protocol: TransportProtocol) : Parcelable
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt
deleted file mode 100644
index 9ec96b1494..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.mullvad.talpid.net
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-data class ObfuscationEndpoint(val endpoint: Endpoint, val obfuscationType: ObfuscationType) :
- Parcelable
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt
deleted file mode 100644
index 72409d9026..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mullvad.talpid.net
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class ObfuscationType : Parcelable {
- Udp2Tcp
-}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt
deleted file mode 100644
index a62abaacd0..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.mullvad.talpid.tunnel
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class ActionAfterDisconnect : Parcelable {
- Nothing,
- Block,
- Reconnect
-}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt
deleted file mode 100644
index 070d190beb..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package net.mullvad.talpid.tunnel
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize data class ErrorState(val cause: ErrorStateCause, val isBlocking: Boolean) : Parcelable
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt
deleted file mode 100644
index fc35e4e23e..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package net.mullvad.talpid.tunnel
-
-import android.os.Parcelable
-import java.net.InetAddress
-import kotlinx.parcelize.Parcelize
-
-private const val AUTH_FAILED_REASON_EXPIRED_ACCOUNT = "[EXPIRED_ACCOUNT]"
-
-sealed class ErrorStateCause : Parcelable {
- @Parcelize
- class AuthFailed(private val reason: String?) : ErrorStateCause() {
- fun isCausedByExpiredAccount(): Boolean {
- return reason == AUTH_FAILED_REASON_EXPIRED_ACCOUNT
- }
- }
-
- @Parcelize data object Ipv6Unavailable : ErrorStateCause()
-
- @Parcelize
- data class SetFirewallPolicyError(val firewallPolicyError: FirewallPolicyError) :
- ErrorStateCause()
-
- @Parcelize data object SetDnsError : ErrorStateCause()
-
- @Parcelize
- data class InvalidDnsServers(val addresses: ArrayList<InetAddress>) : ErrorStateCause()
-
- @Parcelize data object StartTunnelError : ErrorStateCause()
-
- @Parcelize
- data class TunnelParameterError(val error: ParameterGenerationError) : ErrorStateCause()
-
- @Parcelize data object IsOffline : ErrorStateCause()
-
- @Parcelize data object VpnPermissionDenied : ErrorStateCause()
-}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/FirewallPolicyError.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/FirewallPolicyError.kt
deleted file mode 100644
index c6f19e71af..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/FirewallPolicyError.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mullvad.talpid.tunnel
-
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class FirewallPolicyError : Parcelable {
- Generic
-}
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt
deleted file mode 100644
index 148b56eb45..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package net.mullvad.talpid.util
-
-import kotlin.properties.Delegates.observable
-
-// Manages listeners interested in receiving events of type T
-//
-// The listeners subscribe using an ID object. This ID is used later on for unsubscribing. The only
-// requirement is that the object uses the default implementation of the `hashCode` and `equals`
-// methods inherited from `Any` (or `Object` in Java).
-//
-// If the ID object class (or any of its super-classes) overrides `hashCode` or `equals`,
-// unsubscribe might not work correctly.
-class EventNotifier<T>(private val initialValue: T) {
- private val listeners = LinkedHashMap<Any, (T) -> Unit>()
-
- var latestEvent = initialValue
- private set
-
- fun notify(event: T) {
- synchronized(this) {
- latestEvent = event
-
- for (listener in listeners.values) {
- listener(event)
- }
- }
- }
-
- fun notifyIfChanged(event: T) {
- synchronized(this) {
- if (latestEvent != event) {
- notify(event)
- }
- }
- }
-
- fun subscribe(id: Any, listener: (T) -> Unit) {
- subscribe(id, true, listener)
- }
-
- fun subscribe(id: Any, startWithLatestEvent: Boolean, listener: (T) -> Unit) {
- synchronized(this) {
- listeners.put(id, listener)
- if (startWithLatestEvent) listener(latestEvent)
- }
- }
-
- fun hasListeners(): Boolean {
- synchronized(this) {
- return !listeners.isEmpty()
- }
- }
-
- fun unsubscribe(id: Any) {
- synchronized(this) { listeners.remove(id) }
- }
-
- fun unsubscribeAll() {
- synchronized(this) { listeners.clear() }
- }
-
- fun notifiable() = observable(latestEvent) { _, _, newValue -> notify(newValue) }
-}
-
-fun <T> autoSubscribable(id: Any, fallback: T, listener: (T) -> Unit) =
- observable<EventNotifier<T>?>(null) { _, old, new ->
- if (old != new) {
- old?.unsubscribe(id)
-
- if (new == null) {
- listener.invoke(fallback)
- } else {
- new.subscribe(id, listener)
- }
- }
- }
diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt
deleted file mode 100644
index add362fcb1..0000000000
--- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mullvad.talpid.util
-
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-
-fun <T> EventNotifier<T>.callbackFlowFromSubscription(id: Any) = callbackFlow {
- this@callbackFlowFromSubscription.subscribe(id) { this.trySend(it) }
- awaitClose { this@callbackFlowFromSubscription.unsubscribe(id) }
-}