Skip to content

Commit

Permalink
Merge branch 'filter-select-location-relay-list-on-ownership-and-sele…
Browse files Browse the repository at this point in the history
…cted-droid-466'
  • Loading branch information
Pururun committed Nov 8, 2023
2 parents ec17311 + 7b3812a commit 497ea3e
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 387 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.RelayEndpointData
import net.mullvad.mullvadvpn.model.RelayList
Expand Down Expand Up @@ -188,6 +189,6 @@ class SelectLocationScreenTest {
arrayListOf(DUMMY_RELAY_COUNTRY_1, DUMMY_RELAY_COUNTRY_2),
DUMMY_WIREGUARD_ENDPOINT_DATA
)
.toRelayCountries()
.toRelayCountries(ownership = Constraint.Any(), providers = Constraint.Any())
}
}
13 changes: 10 additions & 3 deletions android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.MessageHandler
import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
import net.mullvad.mullvadvpn.usecase.AccountExpiryNotificationUseCase
import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
import net.mullvad.mullvadvpn.usecase.PortRangeUseCase
import net.mullvad.mullvadvpn.usecase.RelayListUseCase
import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase
import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase
import net.mullvad.mullvadvpn.util.ChangelogDataProvider
Expand Down Expand Up @@ -88,25 +91,29 @@ val uiModule = module {
single { TunnelStateNotificationUseCase(get()) }
single { VersionNotificationUseCase(get(), BuildConfig.ENABLE_IN_APP_VERSION_NOTIFICATIONS) }
single { NewDeviceNotificationUseCase(get()) }
single { PortRangeUseCase(get()) }
single { RelayListUseCase(get(), get()) }

single { InAppNotificationController(get(), get(), get(), get(), MainScope()) }

single<IChangelogDataProvider> { ChangelogDataProvider(get()) }

single { RelayListListener(get()) }

// View models
viewModel { AccountViewModel(get(), get(), get()) }
viewModel {
ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG)
}
viewModel { ConnectViewModel(get(), get(), get(), get(), get()) }
viewModel { ConnectViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { DeviceListViewModel(get(), get()) }
viewModel { DeviceRevokedViewModel(get(), get()) }
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { PrivacyDisclaimerViewModel(get()) }
viewModel { SelectLocationViewModel(get()) }
viewModel { SelectLocationViewModel(get(), get()) }
viewModel { SettingsViewModel(get(), get()) }
viewModel { VoucherDialogViewModel(get(), get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) }
viewModel { WelcomeViewModel(get(), get(), get()) }
viewModel { ReportProblemViewModel(get()) }
viewModel { ViewLogsViewModel(get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package net.mullvad.mullvadvpn.relaylist

data class Provider(val name: String, val mullvadOwned: Boolean)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package net.mullvad.mullvadvpn.relaylist

data class RelayList(val country: List<RelayCountry>, val selectedItem: RelayItem?)
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ package net.mullvad.mullvadvpn.relaylist

import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
import net.mullvad.mullvadvpn.model.Ownership
import net.mullvad.mullvadvpn.model.Providers
import net.mullvad.mullvadvpn.model.Relay as DaemonRelay
import net.mullvad.mullvadvpn.model.RelayList

/**
* Convert from a model.RelayList to list of relaylist.RelayCountry Non-wiregaurd relays are
* filtered out So are also cities that only contains non-wireguard relays Countries, cities and
* relays are ordered by name
* filtered out and also relays that do not fit the ownership and provider list So are also cities
* that only contains non-wireguard relays Countries, cities and relays are ordered by name
*/
fun RelayList.toRelayCountries(): List<RelayCountry> {
fun RelayList.toRelayCountries(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>
): List<RelayCountry> {
val relayCountries =
this.countries
.map { country ->
Expand All @@ -27,7 +33,8 @@ fun RelayList.toRelayCountries(): List<RelayCountry> {
relays = relays
)

val validCityRelays = city.relays.filter { relay -> relay.isWireguardRelay }
val validCityRelays =
city.relays.filterValidRelays(ownership = ownership, providers = providers)

for (relay in validCityRelays) {
relays.add(
Expand Down Expand Up @@ -170,6 +177,28 @@ fun List<RelayCountry>.filterOnSearchTerm(
}
}

private fun List<DaemonRelay>.filterValidRelays(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>
): List<DaemonRelay> =
filter { it.isWireguardRelay }
.filter {
when (ownership) {
is Constraint.Any -> true
is Constraint.Only ->
when (ownership.value) {
Ownership.MullvadOwned -> it.owned
Ownership.Rented -> !it.owned
}
}
}
.filter { relay ->
when (providers) {
is Constraint.Any -> true
is Constraint.Only -> providers.value.providers.contains(relay.provider)
}
}

/** Expand the parent(s), if any, for the current selected item */
private fun List<RelayCountry>.expandItemForSelection(
selectedItem: RelayItem?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,135 +1,52 @@
package net.mullvad.mullvadvpn.ui.serviceconnection

import android.os.Messenger
import net.mullvad.mullvadvpn.lib.common.util.toGeographicLocationConstraint
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.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import net.mullvad.mullvadvpn.lib.ipc.Event
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.ipc.Request
import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.model.RelayConstraints
import net.mullvad.mullvadvpn.model.RelaySettings
import net.mullvad.mullvadvpn.model.Ownership
import net.mullvad.mullvadvpn.model.Providers
import net.mullvad.mullvadvpn.model.RelayList
import net.mullvad.mullvadvpn.model.WireguardConstraints
import net.mullvad.mullvadvpn.relaylist.RelayCountry
import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.relaylist.findItemForLocation
import net.mullvad.mullvadvpn.relaylist.toRelayCountries
import net.mullvad.mullvadvpn.model.WireguardEndpointData

class RelayListListener(
private val connection: Messenger,
eventDispatcher: EventDispatcher,
private val settingsListener: SettingsListener
private val messageHandler: MessageHandler,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private var relayCountries: List<RelayCountry>? = null
private var relaySettings: RelaySettings? = null
private var portRanges: List<PortRange> = emptyList()

var selectedRelayItem: RelayItem? = null
private set
val relayListEvents: StateFlow<RelayList> =
messageHandler
.events<Event.NewRelayList>()
.map { it.relayList ?: defaultRelayList() }
// This is added so that we always have a relay list. Otherwise sometimes there would
// not be a relay list since the fetching of a relay list would be done before the
// event stream is available.
.onStart { messageHandler.trySendRequest(Request.FetchRelayList) }
.stateIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, defaultRelayList())

fun updateSelectedRelayLocation(value: GeographicLocationConstraint) {
connection.send(Request.SetRelayLocation(value).message)
messageHandler.trySendRequest(Request.SetRelayLocation(value))
}

fun updateSelectedWireguardConstraints(value: WireguardConstraints) {
connection.send(Request.SetWireguardConstraints(value).message)
}

var onRelayCountriesChange: ((List<RelayCountry>, RelayItem?) -> Unit)? = null
set(value) {
field = value

synchronized(this) {
val relayCountries = this.relayCountries

if (relayCountries != null) {
value?.invoke(relayCountries, selectedRelayItem)
}
}
}

var onPortRangesChange: ((List<PortRange>) -> Unit)? = null
set(value) {
field = value

synchronized(this) { value?.invoke(portRanges) }
}

init {
eventDispatcher.registerHandler(Event.NewRelayList::class) { event ->
event.relayList?.let { relayLocations ->
relayListChanged(relayLocations.toRelayCountries())
portRangesChanged(relayLocations.wireguardEndpointData.portRanges)
}
}

settingsListener.relaySettingsNotifier.subscribe(this) { newRelaySettings ->
relaySettingsChanged(newRelaySettings)
}
}

fun onDestroy() {
settingsListener.relaySettingsNotifier.unsubscribe(this)
onRelayCountriesChange = null
}

private fun relaySettingsChanged(newRelaySettings: RelaySettings?) {
synchronized(this) {
val relayCountries = this.relayCountries
val portRanges = this.portRanges

relaySettings =
newRelaySettings
?: RelaySettings.Normal(
RelayConstraints(
location = Constraint.Any(),
ownership = Constraint.Any(),
wireguardConstraints = WireguardConstraints(Constraint.Any()),
providers = Constraint.Any()
)
)

if (relayCountries != null) {
relayListChanged(relayCountries)
}
portRangesChanged(portRanges)
}
messageHandler.trySendRequest(Request.SetWireguardConstraints(value))
}

private fun relayListChanged(newRelayCountries: List<RelayCountry>) {
synchronized(this) {
relayCountries = newRelayCountries
selectedRelayItem = findSelectedRelayItem()

onRelayCountriesChange?.invoke(newRelayCountries, selectedRelayItem)
}
fun updateSelectedOwnershipFilter(value: Constraint<Ownership>) {
messageHandler.trySendRequest(Request.SetOwnership(value))
}

private fun portRangesChanged(newPortRanges: List<PortRange>) {
synchronized(this) {
portRanges = newPortRanges

onPortRangesChange?.invoke(portRanges)
}
fun updateSelectedProvidersFilter(value: Constraint<Providers>) {
messageHandler.trySendRequest(Request.SetProviders(value))
}

private fun findSelectedRelayItem(): RelayItem? {
val relaySettings = this.relaySettings

when (relaySettings) {
is RelaySettings.CustomTunnelEndpoint -> return null
is RelaySettings.Normal -> {
val location = relaySettings.relayConstraints.location
return relayCountries?.findItemForLocation(
location.toGeographicLocationConstraint()
)
}
else -> {
/* NOOP */
}
}

return null
}
private fun defaultRelayList() = RelayList(ArrayList(), WireguardEndpointData(ArrayList()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class ServiceConnectionContainer(

val appVersionInfoCache = AppVersionInfoCache(dispatcher, settingsListener)
val customDns = CustomDns(connection)
var relayListListener = RelayListListener(connection, dispatcher, settingsListener)

private var listenerId: Int? = null

Expand Down Expand Up @@ -68,7 +67,6 @@ class ServiceConnectionContainer(
voucherRedeemer.onDestroy()

appVersionInfoCache.onDestroy()
relayListListener.onDestroy()
}

private fun registerListener(connection: Messenger) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration
import net.mullvad.mullvadvpn.lib.endpoint.BuildConfig
import net.mullvad.mullvadvpn.lib.endpoint.putApiEndpointConfigurationExtra
Expand Down Expand Up @@ -93,7 +94,7 @@ class ServiceConnectionManager(private val context: Context) : MessageHandler {
}

override fun <E : Event> events(klass: KClass<E>): Flow<E> {
return events.filterIsInstance(klass)
return events.map { it }.filterIsInstance(klass)
}

override fun trySendRequest(request: Request): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ fun ServiceConnectionManager.deviceDataSource() =

fun ServiceConnectionManager.customDns() = this.connectionState.value.readyContainer()?.customDns

fun ServiceConnectionManager.relayListListener() =
this.connectionState.value.readyContainer()?.relayListListener

fun ServiceConnectionManager.settingsListener() =
this.connectionState.value.readyContainer()?.settingsListener

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.mullvad.mullvadvpn.usecase

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import net.mullvad.mullvadvpn.model.PortRange
import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener

class PortRangeUseCase(private val relayListListener: RelayListListener) {
fun portRanges(): Flow<List<PortRange>> =
relayListListener.relayListEvents
.map { it?.wireguardEndpointData?.portRanges ?: emptyList() }
.distinctUntilChanged()
}
Loading

0 comments on commit 497ea3e

Please sign in to comment.