summaryrefslogtreecommitdiffhomepage
path: root/android/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'android/src/main')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt9
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt240
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt23
-rw-r--r--android/src/main/res/layout/settings.xml28
-rw-r--r--android/src/main/res/layout/wireguard_key.xml153
-rw-r--r--android/src/main/res/values/strings.xml17
8 files changed, 463 insertions, 13 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index 15d33fbb04..ffc11354ff 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -99,11 +99,6 @@ class MainActivity : FragmentActivity() {
}
}
- override fun onResume() {
- super.onResume()
- keyStatusListener.onResume()
- }
-
override fun onStop() {
if (shouldStopService) {
runBlocking { service.await().stop() }
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index 06b580d013..940fabbf77 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -35,6 +35,7 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
external fun setAccount(accountToken: String?)
external fun shutdown()
external fun updateRelaySettings(update: RelaySettingsUpdate)
+ external fun verifyWireguardKey(): Boolean?
private external fun initialize(vpnService: MullvadVpnService)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
index ed9c2bf4ea..8465a66140 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
@@ -55,8 +55,11 @@ class SettingsFragment : Fragment() {
view.findViewById<View>(R.id.account).setOnClickListener {
openSubFragment(AccountFragment())
}
+ view.findViewById<View>(R.id.wireguard_keys).setOnClickListener {
+ openSubFragment(WireguardKeyFragment())
+ }
view.findViewById<View>(R.id.app_version).setOnClickListener {
- openLink("https://mullvad.net/download/")
+ openLink(R.string.download_url)
}
view.findViewById<View>(R.id.report_a_problem).setOnClickListener {
openSubFragment(ProblemReportFragment())
@@ -104,8 +107,8 @@ class SettingsFragment : Fragment() {
}
}
- private fun openLink(url: String) {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ private fun openLink(urlResourceId: Int) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(parentActivity.getString(urlResourceId)))
startActivity(intent)
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt
new file mode 100644
index 0000000000..e8be9d7228
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt
@@ -0,0 +1,240 @@
+package net.mullvad.mullvadvpn
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.util.Base64
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ProgressBar
+import android.widget.TextView
+import android.widget.Toast
+
+import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
+import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener
+import net.mullvad.mullvadvpn.model.KeygenEvent
+import net.mullvad.mullvadvpn.model.TunnelState
+
+class WireguardKeyFragment : Fragment() {
+ private var TAG = "keyfragment";
+ private var keyState: KeygenEvent? = null;
+ private var currentJob: Job? = null;
+ private var updateViewsJob: Job? = null;
+ private lateinit var parentActivity: MainActivity
+ private lateinit var connectionProxy: ConnectionProxy
+ private lateinit var keyStatusListener: KeyStatusListener
+ private var generatingKey = false
+ private var validatingKey = false
+
+ private lateinit var publicKey: TextView
+ private lateinit var statusMessage: TextView
+ private lateinit var visitWebsiteView: View
+ private lateinit var actionButton: Button
+ private lateinit var actionSpinner: ProgressBar
+
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ parentActivity = context as MainActivity
+ keyStatusListener = parentActivity.keyStatusListener
+ connectionProxy = parentActivity.connectionProxy
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view = inflater.inflate(R.layout.wireguard_key, container, false)
+
+ view.findViewById<View>(R.id.back).setOnClickListener {
+ parentActivity.onBackPressed()
+ }
+
+
+ statusMessage = view.findViewById<TextView>(R.id.wireguard_key_status)
+ visitWebsiteView = view.findViewById<View>(R.id.wireguard_key_visit_website)
+ publicKey = view.findViewById<TextView>(R.id.wireguard_public_key)
+ actionButton = view.findViewById<Button>(R.id.wg_key_button)
+ actionSpinner = view.findViewById<ProgressBar>(R.id.wg_action_spinner)
+
+ updateViews()
+
+ connectionProxy.onUiStateChange = { _ ->
+ updateViewsJob?.cancel()
+ updateViewsJob = updateViewJob()
+ }
+
+ keyStatusListener.onKeyStatusChange = { _ ->
+ updateViewsJob?.cancel()
+ updateViewsJob = updateViewJob()
+ }
+
+ return view
+ }
+
+ private fun updateViewJob() = GlobalScope.launch(Dispatchers.Main) {
+ updateViews()
+ }
+
+
+ private fun updateViews() {
+ clearErrorMessage()
+ visitWebsiteView.visibility = View.GONE
+
+ actionButton.setClickable(true)
+
+ when (val keyState = keyStatusListener.keyStatus) {
+ null -> {
+ publicKey.visibility = View.INVISIBLE
+ setGenerateButton()
+ }
+ is KeygenEvent.TooManyKeys -> {
+ visitWebsiteView.visibility = View.VISIBLE
+ visitWebsiteView.setOnClickListener {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(parentActivity.getString(R.string.account_url)))
+ startActivity(intent)
+ }
+
+ setStatusMessage(R.string.too_many_keys, R.color.red)
+ setGenerateButton()
+ }
+ is KeygenEvent.GenerationFailure -> {
+ setStatusMessage(R.string.failed_to_generate_key, R.color.red)
+ setGenerateButton()
+ }
+ is KeygenEvent.NewKey -> {
+ val publicKeyString = Base64.encodeToString(keyState.publicKey.key, Base64.DEFAULT)
+ publicKey.visibility = View.VISIBLE
+ publicKey.setText(publicKeyString)
+
+ setValidateButton()
+
+ if (keyState.verified != null) {
+ if (keyState.verified) {
+ setStatusMessage(R.string.wireguard_key_valid, R.color.green)
+ } else {
+ setStatusMessage(R.string.wireguard_key_invalid, R.color.red)
+ setGenerateButton()
+ }
+ }
+ }
+ }
+ drawNoConnectionState()
+ }
+
+ private fun setStatusMessage(message: Int, color: Int) {
+ statusMessage.setText(message)
+ statusMessage.setTextColor(parentActivity.getColor(color))
+ statusMessage.visibility = View.VISIBLE
+ }
+
+ private fun clearErrorMessage() {
+ statusMessage.visibility = View.GONE
+ }
+
+ private fun setGenerateButton() {
+ if (generatingKey) {
+ showActionSpinner()
+ return;
+ }
+ actionSpinner.visibility = View.GONE
+ actionButton.visibility = View.VISIBLE
+ actionButton.setText(R.string.wireguard_generate_key)
+ actionButton.setOnClickListener {
+ onGenerateKeyPress()
+ }
+ }
+
+ private fun setValidateButton() {
+ if (validatingKey) {
+ showActionSpinner()
+ return;
+ }
+ actionSpinner.visibility = View.GONE
+ actionButton.visibility = View.VISIBLE
+ actionButton.setText(R.string.wireguard_validate_key)
+ actionButton.setOnClickListener {
+ onValidateKeyPress()
+ }
+ }
+
+ private fun showActionSpinner() {
+ actionButton.visibility = View.GONE
+ actionSpinner.visibility = View.VISIBLE
+ }
+
+ private fun drawNoConnectionState() {
+ when (connectionProxy.state) {
+ is TunnelState.Connecting, is TunnelState.Disconnecting -> {
+ statusMessage.setText(R.string.wireguard_key_connectivity)
+ statusMessage.visibility = View.VISIBLE
+ actionButton.visibility = View.GONE
+ actionSpinner.visibility = View.VISIBLE
+ }
+ }
+ }
+
+ private fun onGenerateKeyPress() {
+ currentJob?.cancel()
+ generatingKey = true;
+ validatingKey = false;
+ updateViews()
+ currentJob = GlobalScope.launch(Dispatchers.Main) {
+ keyStatusListener.generateKey().join()
+ generatingKey = false;
+ updateViews()
+ }
+ }
+
+ private fun onValidateKeyPress() {
+ currentJob?.cancel()
+ validatingKey = true;
+ generatingKey = false;
+ updateViews()
+ currentJob = GlobalScope.launch(Dispatchers.Main) {
+ keyStatusListener.verifyKey().join()
+ validatingKey = false;
+ when (val state = keyStatusListener.keyStatus) {
+ is KeygenEvent.NewKey -> {
+ if (state.verified == null) {
+ Toast.makeText(parentActivity, R.string.wireguard_key_verification_failure, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ updateViews()
+ }
+ }
+
+ override fun onPause() {
+ connectionProxy.onUiStateChange = null
+ keyStatusListener.onKeyStatusChange = null
+ currentJob?.cancel()
+ updateViewsJob?.cancel()
+ validatingKey = false;
+ generatingKey = false;
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ connectionProxy.onUiStateChange = { _ ->
+ updateViewsJob?.cancel()
+ updateViewsJob = updateViewJob()
+ }
+
+ keyStatusListener.onKeyStatusChange = { _ ->
+ updateViewsJob?.cancel()
+ updateViewsJob = updateViewJob()
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt
index 66b7a96e44..74ad587eea 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/KeyStatusListener.kt
@@ -38,13 +38,26 @@ class KeyStatusListener(val asyncDaemon: Deferred<MullvadDaemon>) {
private fun setUp() = GlobalScope.launch(Dispatchers.Default) {
daemon = asyncDaemon.await()
daemon?.onKeygenEvent = { event -> keyStatus = event }
+ val wireguardKey = daemon?.getWireguardKey()
+ if (wireguardKey != null) {
+ keyStatus = KeygenEvent.NewKey(wireguardKey, null)
+ }
}
- fun onResume() {
- if (keyStatus is KeygenEvent.TooManyKeys || keyStatus is KeygenEvent.GenerationFailure) {
- retryJob?.cancel()
- retryJob = retryKeyGeneration()
- }
+ fun generateKey() = GlobalScope.launch(Dispatchers.Default) {
+ setUpJob.join()
+ keyStatus = daemon?.generateWireguardKey()
+ }
+
+ fun verifyKey() = GlobalScope.launch(Dispatchers.Default) {
+ setUpJob.join()
+ val verified = daemon?.verifyWireguardKey()
+ // Only update verification status if the key is actually there
+ when (val state = keyStatus) {
+ is KeygenEvent.NewKey -> {
+ keyStatus = KeygenEvent.NewKey(state.publicKey, verified)
+ }
+ }
}
fun onDestroy() {
diff --git a/android/src/main/res/layout/settings.xml b/android/src/main/res/layout/settings.xml
index d0308f163b..7b4161406a 100644
--- a/android/src/main/res/layout/settings.xml
+++ b/android/src/main/res/layout/settings.xml
@@ -64,6 +64,34 @@
android:src="@drawable/icon_chevron"
/>
</LinearLayout>
+ <LinearLayout android:id="@+id/wireguard_keys"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="16dp"
+ android:layout_marginTop="24dp"
+ android:background="@drawable/cell_button_background"
+ android:clickable="true"
+ android:gravity="center"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingHorizontal="8dp"
+ android:paddingVertical="17dp"
+ android:textColor="@color/white"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:text="@string/wireguard_key"
+ />
+ <ImageView
+ android:layout_width="14dp"
+ android:layout_height="24dp"
+ android:layout_weight="0"
+ android:alpha="0.6"
+ android:src="@drawable/icon_chevron"
+ />
+ </LinearLayout>
<LinearLayout android:id="@+id/app_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/android/src/main/res/layout/wireguard_key.xml b/android/src/main/res/layout/wireguard_key.xml
new file mode 100644
index 0000000000..b0da0776ca
--- /dev/null
+++ b/android/src/main/res/layout/wireguard_key.xml
@@ -0,0 +1,153 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/darkBlue"
+ android:elevation="3dp"
+ android:gravity="left"
+ android:orientation="vertical">
+
+ <LinearLayout android:id="@+id/back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical | left"
+ android:clickable="true"
+ android:background="?android:attr/selectableItemBackground"
+ >
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginRight="8dp"
+ android:src="@drawable/icon_back"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:text="@string/settings"
+ />
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="24dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="24dp"
+ android:text="@string/wireguard_key"
+ android:textColor="@color/white"
+ android:textSize="32sp"
+ android:textStyle="bold" />
+
+ <LinearLayout
+ android:id="@+id/wireguard_public_key_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/cell_button_background"
+ android:clickable="true"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingHorizontal="4dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="10dp"
+ android:paddingBottom="5dp"
+ android:text="@string/wireguard_public_key"
+ android:textColor="@color/white"
+ android:textSize="20sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/wireguard_public_key"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:paddingTop="3dp"
+ android:textAllCaps="true"
+ android:textColor="@color/white60"
+ android:textSize="14sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/wireguard_key_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="5dp"
+ android:textColor="@color/red"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:visibility="gone" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wireguard_key_visit_website"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:background="@drawable/cell_button_background"
+ android:clickable="true"
+ android:gravity="center"
+ android:paddingHorizontal="16dp"
+ android:visibility="gone">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="8dp"
+ android:paddingVertical="17dp"
+ android:text="@string/wireguard_key_visit_website"
+ android:textColor="@color/white"
+ android:textSize="20sp"
+ android:textStyle="bold" />
+
+ <ImageView
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:alpha="0.6"
+ android:src="@drawable/icon_extlink" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wireguard_button_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
+ android:layout_marginBottom="15dp"
+ android:background="@drawable/cell_button_background"
+ android:clickable="true"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="50dp"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/wg_key_button"
+ style="@style/Button"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:text="@string/wireguard_validate_key"/>
+
+ <ProgressBar
+ android:id="@+id/wg_action_spinner"
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:indeterminate="true"
+ android:indeterminateDrawable="@drawable/icon_spinner"
+ android:indeterminateDuration="600"
+ android:indeterminateOnly="true"
+ android:visibility="gone" />
+ </RelativeLayout>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
index 6b605a7bc2..02f4315083 100644
--- a/android/src/main/res/values/strings.xml
+++ b/android/src/main/res/values/strings.xml
@@ -95,6 +95,23 @@
While connected, your real location is masked with a private and secure location in the
selected region
</string>
+ <string name="wireguard_key">WireGuard key</string>
+ <string name="wireguard_public_key">Public key</string>
+ <string name="wireguard_validate_key">Validate key</string>
+ <string name="wireguard_generate_key">Generate key</string>
+ <string name="wireguard_key_visit_website">Manage your keys on website</string>
+ <string name="wireguard_key_connectivity">
+ Connectivity required to manage your key.
+ </string>
+ <string name="wireguard_key_valid">
+ Key is valid
+ </string>
+ <string name="wireguard_key_invalid">
+ Key is invalid
+ </string>
+ <string name="wireguard_key_verification_failure">
+ Failed to validate key
+ </string>
<string name="account_url">https://mullvad.net/en/account</string>
<string name="download_url">https://mullvad.net/en/download</string>