diff options
Diffstat (limited to 'android/src')
11 files changed, 254 insertions, 48 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt index e9713fff60..a0c94873f4 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt @@ -21,6 +21,8 @@ import net.mullvad.mullvadvpn.dataproxy.RelayListListener import net.mullvad.mullvadvpn.model.KeygenEvent import net.mullvad.mullvadvpn.model.TunnelState +val KEY_IS_TUNNEL_INFO_EXPANDED = "is_tunnel_info_expanded" + class ConnectFragment : Fragment() { private lateinit var actionButton: ConnectActionButton private lateinit var switchLocationButton: SwitchLocationButton @@ -39,6 +41,8 @@ class ConnectFragment : Fragment() { private lateinit var updateKeyStatusJob: Job private lateinit var updateTunnelStateJob: Job + private var isTunnelInfoExpanded = false + override fun onAttach(context: Context) { super.onAttach(context) @@ -50,6 +54,13 @@ class ConnectFragment : Fragment() { versionInfoCache = parentActivity.appVersionInfoCache } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + isTunnelInfoExpanded = + savedInstanceState?.getBoolean(KEY_IS_TUNNEL_INFO_EXPANDED, false) ?: false + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -64,7 +75,9 @@ class ConnectFragment : Fragment() { headerBar = HeaderBar(view, context!!) notificationBanner = NotificationBanner(view, context!!, versionInfoCache) status = ConnectionStatus(view, context!!) - locationInfo = LocationInfo(view, locationInfoCache) + + locationInfo = LocationInfo(view, context!!) + locationInfo.isTunnelInfoExpanded = isTunnelInfoExpanded actionButton = ConnectActionButton(view) actionButton.apply { @@ -90,11 +103,17 @@ class ConnectFragment : Fragment() { override fun onResume() { super.onResume() + locationInfo.isTunnelInfoExpanded = isTunnelInfoExpanded + keyStatusListener.onKeyStatusChange = { keyStatus -> updateKeyStatusJob.cancel() updateKeyStatusJob = updateKeyStatus(keyStatus) } + locationInfoCache.onNewLocation = { location -> + locationInfo.location = location + } + relayListListener.onRelayListChange = { relayList, selectedRelayItem -> switchLocationButton.location = selectedRelayItem } @@ -102,13 +121,15 @@ class ConnectFragment : Fragment() { override fun onPause() { keyStatusListener.onKeyStatusChange = null + locationInfoCache.onNewLocation = null relayListListener.onRelayListChange = null + isTunnelInfoExpanded = locationInfo.isTunnelInfoExpanded + super.onPause() } override fun onDestroyView() { - locationInfo.onDestroy() switchLocationButton.onDestroy() connectionProxy.onUiStateChange = null @@ -117,10 +138,16 @@ class ConnectFragment : Fragment() { super.onDestroyView() } + override fun onSaveInstanceState(state: Bundle) { + isTunnelInfoExpanded = locationInfo.isTunnelInfoExpanded + state.putBoolean(KEY_IS_TUNNEL_INFO_EXPANDED, isTunnelInfoExpanded) + } + private fun updateTunnelState(uiState: TunnelState) = GlobalScope.launch(Dispatchers.Main) { val realState = connectionProxy.state locationInfoCache.state = realState + locationInfo.state = realState headerBar.setState(realState) actionButton.tunnelState = uiState diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/LocationInfo.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/LocationInfo.kt index f1fb1d7860..68e5840550 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/LocationInfo.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/LocationInfo.kt @@ -1,28 +1,137 @@ package net.mullvad.mullvadvpn +import android.content.Context import android.view.View import android.widget.TextView -import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache +import net.mullvad.mullvadvpn.model.Endpoint +import net.mullvad.mullvadvpn.model.GeoIpLocation +import net.mullvad.mullvadvpn.model.TransportProtocol +import net.mullvad.mullvadvpn.model.TunnelState -class LocationInfo(val parentView: View, val locationInfoCache: LocationInfoCache) { - private val countryLabel: TextView = parentView.findViewById(R.id.country) - private val cityLabel: TextView = parentView.findViewById(R.id.city) - private val hostnameLabel: TextView = parentView.findViewById(R.id.hostname) +class LocationInfo(val parentView: View, val context: Context) { + private val country: TextView = parentView.findViewById(R.id.country) + private val city: TextView = parentView.findViewById(R.id.city) + private val tunnelInfo: View = parentView.findViewById(R.id.tunnel_info) + private val hostname: TextView = parentView.findViewById(R.id.hostname) + private val chevron: View = parentView.findViewById(R.id.chevron) + private val protocol: TextView = parentView.findViewById(R.id.tunnel_protocol) + private val inAddress: TextView = parentView.findViewById(R.id.in_address) + private val outAddress: TextView = parentView.findViewById(R.id.out_address) + + private var endpoint: Endpoint? = null + private var isTunnelInfoVisible = false + var isTunnelInfoExpanded = false + + var location: GeoIpLocation? = null + set(value) { + country.text = value?.country ?: "" + city.text = value?.city ?: "" + hostname.text = value?.hostname ?: "" + + updateOutAddress(value) + } + + var state: TunnelState = TunnelState.Disconnected() + set(value) { + field = value + + when (value) { + is TunnelState.Connecting -> { + endpoint = value.endpoint?.endpoint + isTunnelInfoVisible = true + } + is TunnelState.Connected -> { + endpoint = value.endpoint.endpoint + isTunnelInfoVisible = true + } + else -> { + endpoint = null + isTunnelInfoVisible = false + } + } + + updateTunnelInfo() + } init { - locationInfoCache.onNewLocation = { country, city, hostname -> - updateViews(country, city, hostname) + tunnelInfo.setOnClickListener { toggleTunnelInfo() } + } + + private fun toggleTunnelInfo() { + isTunnelInfoExpanded = !isTunnelInfoExpanded + updateTunnelInfo() + } + + private fun updateTunnelInfo() { + if (isTunnelInfoVisible) { + showTunnelInfo() + } else { + hideTunnelInfo() } } - fun onDestroy() { - locationInfoCache.onNewLocation = null + private fun hideTunnelInfo() { + chevron.visibility = View.INVISIBLE + + protocol.text = "" + inAddress.text = "" + outAddress.text = "" } - fun updateViews(country: String, city: String, hostname: String) { - countryLabel.text = country - cityLabel.text = city - hostnameLabel.text = hostname + private fun showTunnelInfo() { + chevron.visibility = View.VISIBLE + + if (isTunnelInfoExpanded) { + chevron.rotation = 180.0F + protocol.setText(R.string.wireguard) + showInAddress(endpoint) + updateOutAddress(location) + } else { + chevron.rotation = 0.0F + protocol.text = "" + inAddress.text = "" + outAddress.text = "" + } + } + + private fun showInAddress(endpoint: Endpoint?) { + if (endpoint != null) { + val transportProtocol = when (endpoint.protocol) { + is TransportProtocol.Tcp -> context.getString(R.string.tcp) + is TransportProtocol.Udp -> context.getString(R.string.udp) + } + + inAddress.text = context.getString( + R.string.in_address, + endpoint.address.address.hostAddress, + endpoint.address.port, + transportProtocol + ) + } else { + inAddress.text = "" + } + } + + private fun updateOutAddress(location: GeoIpLocation?) { + val addressAvailable = location != null && (location.ipv4 != null || location.ipv6 != null) + + if (isTunnelInfoVisible && addressAvailable && isTunnelInfoExpanded) { + val ipv4 = location!!.ipv4 + val ipv6 = location.ipv6 + val ipAddress: String + + if (ipv6 == null) { + ipAddress = ipv4!!.hostAddress + } else if (ipv4 == null) { + ipAddress = ipv6.hostAddress + } else { + ipAddress = "${ipv4.hostAddress} / ${ipv6.hostAddress}" + } + + outAddress.text = context.getString(R.string.out_address, ipAddress) + } else { + outAddress.text = "" + } } } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt index 17c5dff47d..b8b0806285 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt @@ -81,7 +81,7 @@ class ConnectionProxy(val parentActivity: MainActivity) { if (currentState is TunnelState.Connecting || currentState is TunnelState.Connected) { return false } else { - uiState = TunnelState.Connecting(null) + uiState = TunnelState.Connecting(null, null) return true } } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/LocationInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/LocationInfoCache.kt index 6fc655c2a8..2cfa32883b 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/LocationInfoCache.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/LocationInfoCache.kt @@ -22,16 +22,16 @@ class LocationInfoCache( private var lastKnownRealLocation: GeoIpLocation? = null private var activeFetch: Job? = null - var onNewLocation: ((String, String, String) -> Unit)? = null + var onNewLocation: ((GeoIpLocation?) -> Unit)? = null set(value) { field = value - notifyNewLocation() + value?.invoke(location) } var location: GeoIpLocation? = null set(value) { field = value - notifyNewLocation() + onNewLocation?.invoke(value) } var state: TunnelState = TunnelState.Disconnected() @@ -59,22 +59,21 @@ class LocationInfoCache( } } - fun notifyNewLocation() { - val location = this.location - val country = location?.country ?: "" - val city = location?.city ?: "" - val hostname = location?.hostname ?: "" - - onNewLocation?.invoke(country, city, hostname) - } - private fun locationFromSelectedRelay(): GeoIpLocation? { val relayItem = relayListListener.selectedRelayItem when (relayItem) { - is RelayCountry -> return GeoIpLocation(relayItem.name, null, null) - is RelayCity -> return GeoIpLocation(relayItem.country.name, relayItem.name, null) + is RelayCountry -> return GeoIpLocation(null, null, relayItem.name, null, null) + is RelayCity -> return GeoIpLocation( + null, + null, + relayItem.country.name, + relayItem.name, + null + ) is Relay -> return GeoIpLocation( + null, + null, relayItem.city.country.name, relayItem.city.name, relayItem.name diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Endpoint.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Endpoint.kt new file mode 100644 index 0000000000..bf1ca2cad4 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/Endpoint.kt @@ -0,0 +1,5 @@ +package net.mullvad.mullvadvpn.model + +import java.net.InetSocketAddress + +data class Endpoint(val address: InetSocketAddress, val protocol: TransportProtocol) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt index ed7ccb7e66..1f4acef73f 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/GeoIpLocation.kt @@ -1,3 +1,11 @@ package net.mullvad.mullvadvpn.model -data class GeoIpLocation(val country: String, val city: String?, val hostname: String?) +import java.net.InetAddress + +data class GeoIpLocation( + val ipv4: InetAddress?, + val ipv6: InetAddress?, + val country: String, + val city: String?, + val hostname: String? +) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TransportProtocol.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TransportProtocol.kt new file mode 100644 index 0000000000..185461cda7 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TransportProtocol.kt @@ -0,0 +1,6 @@ +package net.mullvad.mullvadvpn.model + +sealed class TransportProtocol { + class Tcp : TransportProtocol() + class Udp : TransportProtocol() +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelEndpoint.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelEndpoint.kt new file mode 100644 index 0000000000..f064218215 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelEndpoint.kt @@ -0,0 +1,3 @@ +package net.mullvad.mullvadvpn.model + +data class TunnelEndpoint(val endpoint: Endpoint) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt index 054a07ed4d..33a40f7196 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt @@ -2,8 +2,8 @@ package net.mullvad.mullvadvpn.model sealed class TunnelState() { class Disconnected() : TunnelState() - class Connecting(val location: GeoIpLocation?) : TunnelState() - class Connected(val location: GeoIpLocation?) : TunnelState() + class Connecting(val endpoint: TunnelEndpoint?, val location: GeoIpLocation?) : TunnelState() + class Connected(val endpoint: TunnelEndpoint, val location: GeoIpLocation?) : TunnelState() class Disconnecting(val actionAfterDisconnect: ActionAfterDisconnect) : TunnelState() class Blocked(val reason: BlockReason) : TunnelState() } diff --git a/android/src/main/res/layout/connect.xml b/android/src/main/res/layout/connect.xml index 4468e838e8..579e3c6a4e 100644 --- a/android/src/main/res/layout/connect.xml +++ b/android/src/main/res/layout/connect.xml @@ -137,13 +137,13 @@ android:layout_height="wrap_content" android:layout_weight="0" android:orientation="vertical" - android:padding="24dp" android:gravity="start" > <TextView android:id="@+id/connection_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="4dp" + android:layout_marginHorizontal="24dp" android:textColor="@color/red" android:textSize="16sp" android:textStyle="bold" @@ -153,6 +153,7 @@ <TextView android:id="@+id/country" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" android:textColor="@color/white" android:textSize="34sp" android:textStyle="bold" @@ -161,26 +162,69 @@ <TextView android:id="@+id/city" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" android:textColor="@color/white" android:textSize="34sp" android:textStyle="bold" android:text="" /> - <TextView android:id="@+id/hostname" - android:layout_width="wrap_content" + + <LinearLayout android:id="@+id/tunnel_info" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:textColor="@color/white" - android:textSize="16sp" - android:textStyle="bold" - android:text="" - /> - </LinearLayout> + android:layout_weight="0" + android:orientation="vertical" + android:paddingHorizontal="24dp" + android:gravity="start" + android:clickable="true" + android:background="?android:attr/selectableItemBackground" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView android:id="@+id/hostname" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="16sp" + android:textStyle="bold" + android:text="" + /> + <ImageView android:id="@+id/chevron" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="5dp" + android:alpha="0.4" + android:src="@drawable/icon_chevron_expand" + /> + </LinearLayout> - <Space - android:layout_width="match_parent" - android:layout_height="32dp" - android:layout_weight="0" - /> + <TextView android:id="@+id/tunnel_protocol" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:textColor="@color/white" + android:textSize="13sp" + android:text="" + /> + <TextView android:id="@+id/in_address" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="13sp" + android:text="" + /> + <TextView android:id="@+id/out_address" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="13sp" + android:text="" + /> + </LinearLayout> + </LinearLayout> <LinearLayout android:layout_width="match_parent" @@ -190,7 +234,7 @@ android:padding="24dp" > <Button android:id="@+id/switch_location" - android:layout_marginVertical="16dp" + android:layout_marginBottom="16dp" android:text="@string/switch_location" android:drawableRight="@drawable/icon_chevron" android:paddingRight="8dp" diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index d419e4abc3..6b605a7bc2 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -60,6 +60,11 @@ <string name="cancel">Cancel</string> <string name="disconnect">Disconnect</string> <string name="switch_location">Switch location</string> + <string name="wireguard">WireGuard</string> + <string name="tcp">TCP</string> + <string name="udp">UDP</string> + <string name="in_address">In %1$s:%2$d %3$s</string> + <string name="out_address">Out %1$s</string> <string name="blocking_internet">Blocking internet</string> <string name="auth_failed">Account authentication failed.</string> |
