summaryrefslogtreecommitdiffhomepage
path: root/android/src
diff options
context:
space:
mode:
Diffstat (limited to 'android/src')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt46
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt36
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt3
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt6
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt45
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt11
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt45
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/model/KeygenEvent.kt7
-rw-r--r--android/src/main/res/layout/connect.xml2
-rw-r--r--android/src/main/res/values/strings.xml5
10 files changed, 185 insertions, 21 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt
index a430ab84c7..00ebdd8f64 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt
@@ -3,12 +3,47 @@ package net.mullvad.mullvadvpn
import android.view.View
import android.widget.Button
+import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.TunnelState
class ConnectActionButton(val parentView: View) {
private val button: Button = parentView.findViewById(R.id.action_button)
- var state: TunnelState = TunnelState.Disconnected()
+ private var enabled = true
+ set(value) {
+ if (field != value) {
+ field = value
+
+ button.setEnabled(value)
+ button.setAlpha(if (value) 1.0F else 0.5F)
+ }
+ }
+
+ private var canConnect = true
+ set(value) {
+ field = value
+ updateEnabled()
+ }
+
+ private var showingConnect = true
+ set(value) {
+ field = value
+ updateEnabled()
+ }
+
+ var keyState: KeygenEvent? = null
+ set(value) {
+ when (value) {
+ null -> canConnect = true
+ is KeygenEvent.NewKey -> canConnect = true
+ is KeygenEvent.TooManyKeys -> canConnect = false
+ is KeygenEvent.GenerationFailure -> canConnect = false
+ }
+
+ field = value
+ }
+
+ var tunnelState: TunnelState = TunnelState.Disconnected()
set(value) {
when (value) {
is TunnelState.Disconnected -> disconnected()
@@ -30,7 +65,7 @@ class ConnectActionButton(val parentView: View) {
}
private fun action() {
- when (state) {
+ when (tunnelState) {
is TunnelState.Disconnected -> onConnect?.invoke()
is TunnelState.Disconnecting -> onConnect?.invoke()
is TunnelState.Connecting -> onCancel?.invoke()
@@ -42,15 +77,22 @@ class ConnectActionButton(val parentView: View) {
private fun disconnected() {
button.setBackgroundResource(R.drawable.green_button_background)
button.setText(R.string.connect)
+ showingConnect = true
}
private fun connecting() {
button.setBackgroundResource(R.drawable.transparent_red_button_background)
button.setText(R.string.cancel)
+ showingConnect = false
}
private fun connected() {
button.setBackgroundResource(R.drawable.transparent_red_button_background)
button.setText(R.string.disconnect)
+ showingConnect = false
+ }
+
+ private fun updateEnabled() {
+ enabled = !showingConnect || canConnect
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
index 9a7c50a9af..a5f14cfa92 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
@@ -14,8 +14,10 @@ import android.view.ViewGroup
import android.widget.ImageButton
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
+import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener
import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache
import net.mullvad.mullvadvpn.dataproxy.RelayListListener
+import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.TunnelState
class ConnectFragment : Fragment() {
@@ -28,16 +30,19 @@ class ConnectFragment : Fragment() {
private lateinit var parentActivity: MainActivity
private lateinit var connectionProxy: ConnectionProxy
+ private lateinit var keyStatusListener: KeyStatusListener
private lateinit var locationInfoCache: LocationInfoCache
private lateinit var relayListListener: RelayListListener
- private var updateViewJob: Job? = null
+ private lateinit var updateKeyStatusJob: Job
+ private lateinit var updateTunnelStateJob: Job
override fun onAttach(context: Context) {
super.onAttach(context)
parentActivity = context as MainActivity
connectionProxy = parentActivity.connectionProxy
+ keyStatusListener = parentActivity.keyStatusListener
locationInfoCache = parentActivity.locationInfoCache
relayListListener = parentActivity.relayListListener
}
@@ -68,13 +73,12 @@ class ConnectFragment : Fragment() {
switchLocationButton = SwitchLocationButton(view)
switchLocationButton.onClick = { openSwitchLocationScreen() }
- updateView(connectionProxy.uiState)
+ updateKeyStatusJob = updateKeyStatus(keyStatusListener.keyStatus)
+ updateTunnelStateJob = updateTunnelState(connectionProxy.uiState)
connectionProxy.onUiStateChange = { uiState ->
- updateViewJob?.cancel()
- updateViewJob = GlobalScope.launch(Dispatchers.Main) {
- updateView(uiState)
- }
+ updateTunnelStateJob.cancel()
+ updateTunnelStateJob = updateTunnelState(uiState)
}
return view
@@ -83,12 +87,18 @@ class ConnectFragment : Fragment() {
override fun onResume() {
super.onResume()
+ keyStatusListener.onKeyStatusChange = { keyStatus ->
+ updateKeyStatusJob.cancel()
+ updateKeyStatusJob = updateKeyStatus(keyStatus)
+ }
+
relayListListener.onRelayListChange = { relayList, selectedRelayItem ->
switchLocationButton.location = selectedRelayItem
}
}
override fun onPause() {
+ keyStatusListener.onKeyStatusChange = null
relayListListener.onRelayListChange = null
super.onPause()
@@ -99,24 +109,28 @@ class ConnectFragment : Fragment() {
switchLocationButton.onDestroy()
connectionProxy.onUiStateChange = null
- updateViewJob?.cancel()
+ updateTunnelStateJob.cancel()
super.onDestroyView()
}
- private fun updateView(uiState: TunnelState) {
+ private fun updateTunnelState(uiState: TunnelState) = GlobalScope.launch(Dispatchers.Main) {
val realState = connectionProxy.state
locationInfoCache.state = realState
headerBar.setState(realState)
- actionButton.state = uiState
+ actionButton.tunnelState = uiState
switchLocationButton.state = uiState
-
- notificationBanner.setState(uiState)
+ notificationBanner.tunnelState = uiState
status.setState(uiState)
}
+ private fun updateKeyStatus(keyStatus: KeygenEvent?) = GlobalScope.launch(Dispatchers.Main) {
+ notificationBanner.keyState = keyStatus
+ actionButton.keyState = keyStatus
+ }
+
private fun openSwitchLocationScreen() {
fragmentManager?.beginTransaction()?.apply {
setCustomAnimations(
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index b75a408ece..f5fbab1526 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -20,6 +20,7 @@ import android.support.v4.app.FragmentActivity
import net.mullvad.mullvadvpn.dataproxy.AccountCache
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
+import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener
import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
import net.mullvad.mullvadvpn.dataproxy.RelayListListener
@@ -36,6 +37,7 @@ class MainActivity : FragmentActivity() {
private set
val connectionProxy = ConnectionProxy(this)
+ val keyStatusListener = KeyStatusListener(daemon)
val locationInfoCache = LocationInfoCache(daemon)
val problemReport = MullvadProblemReport()
var settingsListener = SettingsListener(this)
@@ -93,6 +95,7 @@ class MainActivity : FragmentActivity() {
override fun onDestroy() {
accountCache.onDestroy()
+ keyStatusListener.onDestroy()
relayListListener.onDestroy()
settingsListener.onDestroy()
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index eacedcdb69..769a2f1ad9 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn
import net.mullvad.mullvadvpn.model.AccountData
import net.mullvad.mullvadvpn.model.GeoIpLocation
+import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.PublicKey
import net.mullvad.mullvadvpn.model.RelayList
import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
@@ -14,6 +15,7 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
initialize(vpnService)
}
+ var onKeygenEvent: ((KeygenEvent) -> Unit)? = null
var onRelayListChange: ((RelayList) -> Unit)? = null
var onSettingsChange: ((Settings) -> Unit)? = null
var onTunnelStateChange: ((TunnelState) -> Unit)? = null
@@ -32,6 +34,10 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
private external fun initialize(vpnService: MullvadVpnService)
+ private fun notifyKeygenEvent(event: KeygenEvent) {
+ onKeygenEvent?.invoke(event)
+ }
+
private fun notifyRelayListEvent(relayList: RelayList) {
onRelayListChange?.invoke(relayList)
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
index 1af739dcc6..b90b3aa251 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
@@ -1,30 +1,65 @@
package net.mullvad.mullvadvpn
+import android.widget.TextView
import android.view.View
+import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.TunnelState
class NotificationBanner(val parentView: View) {
private val banner: View = parentView.findViewById(R.id.notification_banner)
+ private val title: TextView = parentView.findViewById(R.id.notification_title)
+
private var visible = false
- fun setState(state: TunnelState) {
- when (state) {
+ var keyState: KeygenEvent? = null
+ set(value) {
+ field = value
+ update()
+ }
+
+ var tunnelState: TunnelState = TunnelState.Disconnected()
+ set(value) {
+ field = value
+ update()
+ }
+
+ private fun update() {
+ updateBasedOnKeyState() || updateBasedOnTunnelState()
+ }
+
+ private fun updateBasedOnKeyState(): Boolean {
+ when (keyState) {
+ null -> return false
+ is KeygenEvent.NewKey -> return false
+ is KeygenEvent.TooManyKeys -> show(R.string.too_many_keys)
+ is KeygenEvent.GenerationFailure -> show(R.string.failed_to_generate_key)
+ }
+
+ return true
+ }
+
+ private fun updateBasedOnTunnelState(): Boolean {
+ when (tunnelState) {
is TunnelState.Disconnecting -> hide()
is TunnelState.Disconnected -> hide()
- is TunnelState.Connecting -> show()
+ is TunnelState.Connecting -> show(R.string.blocking_internet)
is TunnelState.Connected -> hide()
- is TunnelState.Blocked -> show()
+ is TunnelState.Blocked -> show(R.string.blocking_internet)
}
+
+ return true
}
- private fun show() {
+ private fun show(message: Int) {
if (!visible) {
visible = true
banner.visibility = View.VISIBLE
banner.translationY = -banner.height.toFloat()
banner.animate().translationY(0.0F).setDuration(350).start()
}
+
+ title.setText(message)
}
private fun hide() {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
index eae0ed75f2..4a1849318c 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
@@ -19,6 +19,7 @@ import android.widget.ViewSwitcher
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
import net.mullvad.mullvadvpn.dataproxy.RelayListListener
import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.LocationConstraint
import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
import net.mullvad.mullvadvpn.relaylist.RelayItem
@@ -40,7 +41,7 @@ class SelectLocationFragment : Fragment() {
init {
relayListAdapter.onSelect = { relayItem ->
updateLocationConstraint(relayItem)
- connectionProxy.connect()
+ maybeConnect()
close()
}
}
@@ -123,4 +124,12 @@ class SelectLocationFragment : Fragment() {
relayListContainer.showNext()
}
}
+
+ private fun maybeConnect() {
+ val keyStatus = parentActivity.keyStatusListener.keyStatus
+
+ if (keyStatus == null || keyStatus is KeygenEvent.NewKey) {
+ connectionProxy.connect()
+ }
+ }
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt
new file mode 100644
index 0000000000..1279f59567
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt
@@ -0,0 +1,45 @@
+package net.mullvad.mullvadvpn.dataproxy
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+
+import net.mullvad.mullvadvpn.MullvadDaemon
+import net.mullvad.mullvadvpn.model.KeygenEvent
+
+class KeyStatusListener(val asyncDaemon: Deferred<MullvadDaemon>) {
+ private var daemon: MullvadDaemon? = null
+
+ private val setUpJob = setUp()
+
+ var keyStatus: KeygenEvent? = null
+ private set(value) {
+ synchronized(this) {
+ field = value
+
+ if (value != null) {
+ onKeyStatusChange?.invoke(value)
+ }
+ }
+ }
+
+ var onKeyStatusChange: ((KeygenEvent) -> Unit)? = null
+ set(value) {
+ field = value
+
+ synchronized(this) {
+ keyStatus?.let { status -> value?.invoke(status) }
+ }
+ }
+
+ private fun setUp() = GlobalScope.launch(Dispatchers.Default) {
+ daemon = asyncDaemon.await()
+ daemon?.onKeygenEvent = { event -> keyStatus = event }
+ }
+
+ fun onDestroy() {
+ setUpJob.cancel()
+ daemon?.onKeygenEvent = null
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/KeygenEvent.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/KeygenEvent.kt
new file mode 100644
index 0000000000..b5938d65f6
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/KeygenEvent.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.model
+
+sealed class KeygenEvent {
+ class NewKey(var publicKey: PublicKey) : KeygenEvent()
+ class TooManyKeys : KeygenEvent()
+ class GenerationFailure : KeygenEvent()
+}
diff --git a/android/src/main/res/layout/connect.xml b/android/src/main/res/layout/connect.xml
index 5c2beb1e2e..38298d2882 100644
--- a/android/src/main/res/layout/connect.xml
+++ b/android/src/main/res/layout/connect.xml
@@ -62,7 +62,7 @@
android:layout_marginLeft="19dp"
android:src="@drawable/icon_notification_error"
/>
- <TextView
+ <TextView android:id="@+id/notification_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
index a3da5d4e96..711be12793 100644
--- a/android/src/main/res/values/strings.xml
+++ b/android/src/main/res/values/strings.xml
@@ -54,12 +54,15 @@
<string name="unsecured_connection">Unsecured connection</string>
<string name="creating_secure_connection">Creating secure connection</string>
<string name="secure_connection">Secure connection</string>
- <string name="blocking_internet">Blocking internet</string>
<string name="connect">Secure my connection</string>
<string name="cancel">Cancel</string>
<string name="disconnect">Disconnect</string>
<string name="switch_location">Switch location</string>
+ <string name="blocking_internet">Blocking internet</string>
+ <string name="too_many_keys">Too many WireGuard keys registered to account</string>
+ <string name="failed_to_generate_key">Failed to generate WireGuard key</string>
+
<string name="select_location">Select location</string>
<string name="select_location_description">
While connected, your real location is masked with a private and secure location in the