summaryrefslogtreecommitdiffhomepage
path: root/ui/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/main')
-rw-r--r--ui/src/main/AndroidManifest.xml27
-rw-r--r--ui/src/main/java/com/wireguard/android/Application.kt6
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt3
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt3
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt33
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt27
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt133
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelManager.kt5
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt58
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt22
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt72
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt85
-rw-r--r--ui/src/main/res/drawable/ic_action_add_white.xml7
-rw-r--r--ui/src/main/res/drawable/ic_action_delete.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_edit.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_edit_white.xml9
-rw-r--r--ui/src/main/res/drawable/ic_action_generate.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_open.xml (renamed from ui/src/main/res/drawable/ic_action_open_white.xml)3
-rw-r--r--ui/src/main/res/drawable/ic_action_save.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_scan_qr_code.xml (renamed from ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml)3
-rw-r--r--ui/src/main/res/drawable/ic_action_select_all.xml3
-rw-r--r--ui/src/main/res/drawable/ic_action_share_white.xml1
-rw-r--r--ui/src/main/res/drawable/ic_arrow_back.xml3
-rw-r--r--ui/src/main/res/drawable/ic_settings.xml3
-rw-r--r--ui/src/main/res/layout/add_tunnels_bottom_sheet.xml4
-rw-r--r--ui/src/main/res/layout/http_proxy_menu_item.xml8
-rw-r--r--ui/src/main/res/layout/tunnel_detail_fragment.xml120
-rw-r--r--ui/src/main/res/layout/tunnel_detail_peer.xml34
-rw-r--r--ui/src/main/res/layout/tunnel_editor_fragment.xml105
-rw-r--r--ui/src/main/res/values/strings.xml14
-rw-r--r--ui/src/main/res/values/styles.xml17
-rw-r--r--ui/src/main/res/values/tv_styles.xml18
33 files changed, 739 insertions, 101 deletions
diff --git a/ui/src/main/AndroidManifest.xml b/ui/src/main/AndroidManifest.xml
index 4dd38cb2..405a3bed 100644
--- a/ui/src/main/AndroidManifest.xml
+++ b/ui/src/main/AndroidManifest.xml
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.wireguard.android"
android:installLocation="internalOnly">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission
@@ -21,6 +22,9 @@
<uses-feature
android:name="android.hardware.camera.any"
android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera"
+ android:required="false" />
<permission
android:name="${applicationId}.permission.CONTROL_TUNNELS"
@@ -44,7 +48,7 @@
android:theme="@style/NoBackgroundTheme"
android:excludeFromRecents="true"/>
- <activity android:name=".activity.MainActivity">
+ <activity android:name=".activity.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -58,7 +62,8 @@
<activity
android:name=".activity.TvMainActivity"
- android:theme="@style/TvTheme">
+ android:theme="@style/TvTheme"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
@@ -82,7 +87,8 @@
<activity
android:name=".activity.LogViewerActivity"
- android:label="@string/log_viewer_title">
+ android:label="@string/log_viewer_title"
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -94,7 +100,7 @@
android:exported="false"
android:grantUriPermissions="true" />
- <receiver android:name=".BootShutdownReceiver">
+ <receiver android:name=".BootShutdownReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -103,7 +109,8 @@
<receiver
android:name=".model.TunnelManager$IntentReceiver"
- android:permission="${applicationId}.permission.CONTROL_TUNNELS">
+ android:permission="${applicationId}.permission.CONTROL_TUNNELS"
+ android:exported="true">
<intent-filter>
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
@@ -114,7 +121,8 @@
<service
android:name=".QuickTileService"
android:icon="@drawable/ic_tile"
- android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -135,5 +143,10 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
+
+ <intent>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+ </intent>
</queries>
</manifest>
diff --git a/ui/src/main/java/com/wireguard/android/Application.kt b/ui/src/main/java/com/wireguard/android/Application.kt
index d9dcb637..7573bdad 100644
--- a/ui/src/main/java/com/wireguard/android/Application.kt
+++ b/ui/src/main/java/com/wireguard/android/Application.kt
@@ -16,6 +16,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
+import com.google.android.material.color.DynamicColors
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
@@ -64,11 +65,9 @@ class Application : android.app.Application() {
private suspend fun determineBackend(): Backend {
var backend: Backend? = null
- var didStartRootShell = false
if (UserKnobs.enableKernelModule.first() && WgQuickBackend.hasKernelSupport()) {
try {
- if (!didStartRootShell)
- rootShell.start()
+ rootShell.start()
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
backend = wgQuickBackend
@@ -88,6 +87,7 @@ class Application : android.app.Application() {
override fun onCreate() {
Log.i(TAG, USER_AGENT)
super.onCreate()
+ DynamicColors.applyToActivitiesIfAvailable(this)
rootShell = RootShell(applicationContext)
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
diff --git a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
index f6f57ddb..1a40a1cd 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -15,6 +15,7 @@ import androidx.databinding.Observable
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.lifecycleScope
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
import com.wireguard.android.BR
import com.wireguard.android.R
@@ -88,7 +89,7 @@ class AppListDialogFragment : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val alertDialogBuilder = AlertDialog.Builder(requireActivity())
+ val alertDialogBuilder = MaterialAlertDialogBuilder(requireActivity())
val binding = AppListDialogFragmentBinding.inflate(requireActivity().layoutInflater, null, false)
binding.executePendingBindings()
alertDialogBuilder.setView(binding.root)
diff --git a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
index d20f21c6..0c4e9d20 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.wireguard.android.Application
import com.wireguard.android.R
@@ -64,7 +65,7 @@ class ConfigNamingDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val activity = requireActivity()
imm = activity.getSystemService()
- val alertDialogBuilder = AlertDialog.Builder(activity)
+ val alertDialogBuilder = MaterialAlertDialogBuilder(activity)
alertDialogBuilder.setTitle(R.string.import_from_qr_code)
binding = ConfigNamingDialogFragmentBinding.inflate(activity.layoutInflater, null, false)
binding?.apply {
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
index 7046cb96..50d43690 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
@@ -5,6 +5,7 @@
package com.wireguard.android.fragment
import android.os.Bundle
+import android.util.Log;
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -18,8 +19,12 @@ import com.wireguard.android.databinding.TunnelDetailFragmentBinding
import com.wireguard.android.databinding.TunnelDetailPeerBinding
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.QuantityFormatter
+import com.wireguard.android.viewmodel.ConfigDetail
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
/**
* Fragment that shows details about a specific tunnel.
@@ -70,7 +75,9 @@ class TunnelDetailFragment : BaseFragment() {
} else {
lifecycleScope.launch {
try {
- binding.config = newTunnel.getConfigAsync()
+ var config = newTunnel.getConfigDetailAsync()
+ binding.config = config
+ Log.i(TAG, "onSelectedTunnelChanged " + config + ", " + config.config)
} catch (_: Throwable) {
binding.config = null
}
@@ -99,6 +106,7 @@ class TunnelDetailFragment : BaseFragment() {
val state = tunnel.state
if (state != Tunnel.State.UP && lastState == state) return
lastState = state
+ var now = LocalDateTime.now(ZoneId.of("UTC"))
try {
val statistics = tunnel.getStatisticsAsync()
for (i in 0 until binding.peersLayout.childCount) {
@@ -110,11 +118,20 @@ class TunnelDetailFragment : BaseFragment() {
if (rx == 0L && tx == 0L) {
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
- continue
+ } else {
+ peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
+ peer.transferLabel.visibility = View.VISIBLE
+ peer.transferText.visibility = View.VISIBLE
+ }
+ val lastHandshake:LocalDateTime? = statistics.peerLastHandshake(publicKey)
+ if (lastHandshake == null) {
+ peer.lastHandshakeLabel.visibility = View.GONE
+ peer.lastHandshakeText.visibility = View.GONE
+ } else {
+ peer.lastHandshakeText.text = getString(R.string.last_handshake_ago, QuantityFormatter.formatDuration(Duration.between(lastHandshake, now)))
+ peer.lastHandshakeLabel.visibility = View.VISIBLE
+ peer.lastHandshakeText.visibility = View.VISIBLE
}
- peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
- peer.transferLabel.visibility = View.VISIBLE
- peer.transferText.visibility = View.VISIBLE
}
} catch (e: Throwable) {
for (i in 0 until binding.peersLayout.childCount) {
@@ -122,7 +139,13 @@ class TunnelDetailFragment : BaseFragment() {
?: continue
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
+ peer.lastHandshakeLabel.visibility = View.GONE
+ peer.lastHandshakeText.visibility = View.GONE
}
}
}
+
+ companion object {
+ private const val TAG = "WireGuard/TunnelDetailFragment"
+ }
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
index 6c6f53f9..1b7cf224 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
@@ -16,10 +16,14 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
+import android.widget.ArrayAdapter
+import android.widget.AutoCompleteTextView
import android.widget.EditText
+import android.widget.Filter
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.textfield.TextInputLayout
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.backend.Tunnel
@@ -29,6 +33,7 @@ import com.wireguard.android.util.AdminKnobs
import com.wireguard.android.util.BiometricAuthenticator
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.viewmodel.ConfigProxy
+import com.wireguard.android.viewmodel.Constants
import com.wireguard.config.Config
import kotlinx.coroutines.launch
@@ -40,6 +45,21 @@ class TunnelEditorFragment : BaseFragment() {
private var binding: TunnelEditorFragmentBinding? = null
private var tunnel: ObservableTunnel? = null
+ private class MaterialSpinnerAdapter<T>(context: Context, resource: Int, private val objects: List<T>) : ArrayAdapter<T>(context, resource, objects) {
+ private val _filter: Filter by lazy {
+ object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ return FilterResults()
+ }
+
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ }
+ }
+ }
+
+ override fun getFilter(): Filter = _filter
+ }
+
private fun onConfigLoaded(config: Config) {
binding?.config = ConfigProxy(config)
}
@@ -80,6 +100,13 @@ class TunnelEditorFragment : BaseFragment() {
executePendingBindings()
privateKeyTextLayout.setEndIconOnClickListener { config?.`interface`?.generateKeyPair() }
}
+
+ var httpProxyMenu = binding?.root?.findViewById<TextInputLayout>(R.id.http_proxy_menu)
+ var httpProxyItems = listOf(Constants.HTTP_PROXY_NONE, Constants.HTTP_PROXY_MANUAL, Constants.HTTP_PROXY_PAC)
+ var httpProxyAdapter = MaterialSpinnerAdapter(requireContext(), R.layout.http_proxy_menu_item, httpProxyItems)
+ var httpProxyMenuText = httpProxyMenu?.editText as? AutoCompleteTextView
+ httpProxyMenuText?.setAdapter(httpProxyAdapter)
+
return binding?.root
}
diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
index 252e8759..9684ffab 100644
--- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
@@ -7,12 +7,20 @@ package com.wireguard.android.model
import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
+import com.wireguard.android.Application
import com.wireguard.android.BR
+import com.wireguard.android.backend.Dhcp
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.Keyed
import com.wireguard.android.util.applicationScope
+import com.wireguard.android.viewmodel.ConfigDetail
+import com.wireguard.android.viewmodel.PeerDetail
import com.wireguard.config.Config
+import com.wireguard.config.InetEndpoint
+import com.wireguard.config.InetNetwork
+import com.wireguard.crypto.Key
+import java.util.Optional
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -55,7 +63,18 @@ class ObservableTunnel internal constructor(
}
fun onStateChanged(state: Tunnel.State): Tunnel.State {
- if (state != Tunnel.State.UP) onStatisticsChanged(null)
+ if (state != Tunnel.State.UP) {
+ onStatisticsChanged(null)
+ onDhcpChanged(null)
+ Application.getCoroutineScope().launch {
+ onPeersReset()
+ }
+ } else {
+ configDetail?.peers?.forEach {
+ var endpoint: InetEndpoint? = it.peer?.endpoint?.orElse(null)
+ it.endpoint = Optional.ofNullable(endpoint?.getResolved()?.get())
+ }
+ }
this.state = state
notifyPropertyChanged(BR.state)
return state
@@ -68,6 +87,7 @@ class ObservableTunnel internal constructor(
this@ObservableTunnel.state
}
+ private var configDetail: ConfigDetail? = if (config != null) ConfigDetail(config) else null
@get:Bindable
var config = config
@@ -86,7 +106,11 @@ class ObservableTunnel internal constructor(
private set
suspend fun getConfigAsync(): Config = withContext(Dispatchers.Main.immediate) {
- config ?: manager.getTunnelConfig(this@ObservableTunnel)
+ config ?: manager.getTunnelConfig(this@ObservableTunnel).config!!
+ }
+
+ suspend fun getConfigDetailAsync(): ConfigDetail = withContext(Dispatchers.Main.immediate) {
+ configDetail ?: manager.getTunnelConfig(this@ObservableTunnel)
}
suspend fun setConfigAsync(config: Config): Config = withContext(Dispatchers.Main.immediate) {
@@ -98,10 +122,11 @@ class ObservableTunnel internal constructor(
}
}
- fun onConfigChanged(config: Config?): Config? {
+ fun onConfigChanged(config: Config?): ConfigDetail? {
+ this.configDetail = ConfigDetail(config)
this.config = config
notifyPropertyChanged(BR.config)
- return config
+ return configDetail
}
@@ -137,6 +162,106 @@ class ObservableTunnel internal constructor(
}
+ @get:Bindable
+ var dhcp: Dhcp? = null
+ private set
+
+ override fun onDhcpChange(newDhcp: Dhcp) {
+ onDhcpChanged(newDhcp)
+ }
+
+ fun onDhcpChanged(dhcp: Dhcp?): Dhcp? {
+ this.dhcp = dhcp
+ notifyPropertyChanged(BR.dhcp)
+ return dhcp
+ }
+
+ // Remove dynamic peers, and reset static peers
+ fun onPeersReset() {
+ Log.i(TAG, "ObservableTunnel onPeersReset")
+ var toRemove: MutableList<PeerDetail> = ArrayList()
+
+ configDetail?.peers?.forEach {
+ if (it.peer == null) {
+ toRemove.add(it)
+ } else {
+ it.endpoint = Optional.empty()
+ }
+ }
+
+ toRemove.forEach {
+ Log.i(TAG, "ObservableTunnel remove " + it)
+ configDetail?.peers?.remove(it)
+ }
+ }
+
+ override fun onEndpointChange(publicKey: Key, newEndpoint: InetEndpoint?) {
+ Application.getCoroutineScope().launch {
+ onEndpointChanged(publicKey, newEndpoint)
+ }
+ }
+
+ private fun onEndpointChanged(publicKey: Key, newEndpoint: InetEndpoint?) {
+
+ Log.i(TAG, "ObservableTunnel onEndpointChange " + newEndpoint)
+ var peer: PeerDetail? = null
+
+ configDetail?.peers?.forEach {
+ if (it.publicKey.equals(publicKey) == true) {
+ Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer)
+ peer = it;
+ }
+ }
+
+ if (peer == null) {
+ Log.i(TAG, "ObservableTunnel create peer " + publicKey)
+ peer = PeerDetail(publicKey)
+ configDetail?.peers?.add(peer)
+ }
+
+ var peer2: PeerDetail = peer!!
+
+ if (newEndpoint != null) {
+ peer2.endpoint = newEndpoint.getResolved()
+ } else {
+ var peer3 = peer2.peer
+ peer2.endpoint = if (peer3 != null) peer3.endpoint else Optional.empty()
+ }
+ }
+
+ fun lookupPeer(publicKey: Key): PeerDetail {
+ configDetail?.peers?.forEach {
+ if (it.publicKey.equals(publicKey) == true) {
+ Log.i(TAG, "ObservableTunnel peer " + it + ", " + it.peer)
+ return it
+ }
+ }
+
+ Log.i(TAG, "ObservableTunnel create peer " + publicKey)
+ var peer: PeerDetail = PeerDetail(publicKey)
+ configDetail?.peers?.add(peer)
+
+ return peer
+ }
+
+ override fun onAllowedIpsChange(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) {
+ Application.getCoroutineScope().launch {
+ onAllowedIpsChanged(publicKey, addNetworks, removeNetworks)
+ }
+ }
+
+ private fun onAllowedIpsChanged(publicKey: Key, addNetworks: List<InetNetwork>?, removeNetworks: List<InetNetwork>?) {
+ var peer: PeerDetail = lookupPeer(publicKey)
+
+ removeNetworks?.let() {
+ peer.allowedIps.removeAll(removeNetworks)
+ }
+ addNetworks?.let() {
+ peer.allowedIps.addAll(addNetworks)
+ }
+ }
+
+
suspend fun deleteAsync() = manager.delete(this)
diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
index ec796164..5671aa7a 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
@@ -24,6 +24,7 @@ import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.applicationScope
+import com.wireguard.android.viewmodel.ConfigDetail
import com.wireguard.config.Config
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
@@ -94,7 +95,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
applicationScope.launch { UserKnobs.setLastUsedTunnel(value?.name) }
}
- suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
+ suspend fun getTunnelConfig(tunnel: ObservableTunnel): ConfigDetail = withContext(Dispatchers.Main.immediate) {
tunnel.onConfigChanged(withContext(Dispatchers.IO) { configStore.load(tunnel.name) })!!
}
@@ -155,7 +156,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
tunnel.onConfigChanged(withContext(Dispatchers.IO) {
getBackend().setState(tunnel, tunnel.state, config)
configStore.save(tunnel.name, config)
- })!!
+ })!!.config!!
}
suspend fun setTunnelName(tunnel: ObservableTunnel, name: String): String = withContext(Dispatchers.Main.immediate) {
diff --git a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
index 60c6b878..d42f2434 100644
--- a/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
+++ b/ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt
@@ -114,6 +114,8 @@ object ErrorMessages {
return resources.getString(R.string.bad_config_explanation_udp_port)
} else if (bce.location == BadConfigException.Location.MTU) {
return resources.getString(R.string.bad_config_explanation_positive_number)
+ } else if (bce.location == BadConfigException.Location.HTTP_PROXY) {
+ return resources.getString(R.string.bad_config_explanation_http_proxy)
} else if (bce.location == BadConfigException.Location.PERSISTENT_KEEPALIVE) {
return resources.getString(R.string.bad_config_explanation_pka)
}
diff --git a/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt b/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
index 4a9ffed4..cd2adbcb 100644
--- a/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
+++ b/ui/src/main/java/com/wireguard/android/util/QuantityFormatter.kt
@@ -8,6 +8,8 @@ package com.wireguard.android.util
import com.wireguard.android.Application
import com.wireguard.android.R
+import java.time.Duration
+
object QuantityFormatter {
fun formatBytes(bytes: Long): String {
val context = Application.get().applicationContext
@@ -19,4 +21,58 @@ object QuantityFormatter {
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
}
}
-} \ No newline at end of file
+
+ fun formatDuration(duration: Duration): String {
+ val context = Application.get().applicationContext
+ val str = formatHours(duration.getSeconds())
+ return when {
+ str != "" -> str
+ else -> context.getString(R.string.duration_seconds, 0)
+ }
+ }
+
+ fun formatHours(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val hours = seconds / 3600
+ val restSeconds = seconds % 3600
+ val str = formatMinutes(restSeconds)
+
+ return when {
+ hours > 0 -> context.getString(R.string.duration_hours, hours) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatDays(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val days = seconds / 3600 / 24
+ val restSeconds = seconds % (3600 * 24)
+ val str = formatHours(restSeconds)
+
+ return when {
+ days > 0 -> context.getString(R.string.duration_days, days) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatMinutes(seconds: Long): String {
+ val context = Application.get().applicationContext
+ val minutes = seconds / 60
+ val restSeconds = seconds % 60
+ val str = formatSeconds(restSeconds)
+
+ return when {
+ minutes > 0 -> context.getString(R.string.duration_minutes, minutes) + if (str != "") ", " + str else ""
+ else -> str
+ }
+ }
+
+ fun formatSeconds(seconds: Long): String {
+ val context = Application.get().applicationContext
+
+ return when {
+ seconds > 0 -> context.getString(R.string.duration_seconds, seconds)
+ else -> ""
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt
new file mode 100644
index 00000000..af95a86a
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/ConfigDetail.kt
@@ -0,0 +1,22 @@
+package com.wireguard.android.viewmodel
+
+import androidx.databinding.ObservableArrayList
+import androidx.databinding.ObservableList
+
+import com.wireguard.config.Config
+
+class ConfigDetail {
+ val config: Config?
+ val peers: ObservableList<PeerDetail> = ObservableArrayList()
+
+ constructor(other: Config?) {
+ config = other
+ if (other != null) {
+ other.peers.forEach {
+ val detail = PeerDetail(it)
+ peers.add(detail)
+ detail.bind(this)
+ }
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
index 2792f749..1833d5c8 100644
--- a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
@@ -4,6 +4,8 @@
*/
package com.wireguard.android.viewmodel
+import android.net.Uri
+import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.databinding.BaseObservable
@@ -18,6 +20,12 @@ import com.wireguard.crypto.Key
import com.wireguard.crypto.KeyFormatException
import com.wireguard.crypto.KeyPair
+object Constants {
+ const val HTTP_PROXY_NONE = "None"
+ const val HTTP_PROXY_MANUAL = "Manual"
+ const val HTTP_PROXY_PAC = "Proxy Auto-Config"
+}
+
class InterfaceProxy : BaseObservable, Parcelable {
@get:Bindable
val excludedApplications: ObservableList<String> = ObservableArrayList()
@@ -54,6 +62,44 @@ class InterfaceProxy : BaseObservable, Parcelable {
}
@get:Bindable
+ var httpProxyMenu: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyMenu)
+ notifyPropertyChanged(BR.httpProxyManualVisibility)
+ notifyPropertyChanged(BR.httpProxyPacVisibility)
+ }
+
+ @get:Bindable
+ var httpProxyManualVisibility: Int = 0
+ get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) android.view.View.GONE else (if (httpProxyMenu == Constants.HTTP_PROXY_MANUAL) android.view.View.VISIBLE else android.view.View.GONE)
+
+ @get:Bindable
+ var httpProxyHostname: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyHostname)
+ }
+
+ @get:Bindable
+ var httpProxyPort: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyPort)
+ }
+
+ @get:Bindable
+ var httpProxyPac: String = ""
+ set(value) {
+ field = value
+ notifyPropertyChanged(BR.httpProxyPac)
+ }
+
+ @get:Bindable
+ var httpProxyPacVisibility: Int = 0
+ get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) android.view.View.GONE else (if (httpProxyMenu == Constants.HTTP_PROXY_PAC) android.view.View.VISIBLE else android.view.View.GONE)
+
+ @get:Bindable
var privateKey: String = ""
set(value) {
field = value
@@ -76,6 +122,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
parcel.readStringList(includedApplications)
listenPort = parcel.readString() ?: ""
mtu = parcel.readString() ?: ""
+ httpProxyMenu = parcel.readString() ?: ""
+ httpProxyHostname = parcel.readString() ?: ""
+ httpProxyPort = parcel.readString() ?: ""
+ httpProxyPac = parcel.readString() ?: ""
privateKey = parcel.readString() ?: ""
}
@@ -87,6 +137,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
includedApplications.addAll(other.includedApplications)
listenPort = other.listenPort.map { it.toString() }.orElse("")
mtu = other.mtu.map { it.toString() }.orElse("")
+ httpProxyHostname = other.httpProxy.map { if (it.getHost().startsWith('[') && it.getHost().endsWith(']')) it.getHost().substring(1, it.getHost().length-1) else it.getHost() }.orElse("")
+ httpProxyPort = other.httpProxy.map { if (it.getPort() <= 0) "8080" else it.getPort().toString() }.orElse("")
+ httpProxyPac = other.httpProxy.map { it.getPacFileUrl().toString() }.orElse("")
+ httpProxyMenu = other.httpProxy.map { if (it.getPacFileUrl() != null && it.getPacFileUrl() != Uri.EMPTY) Constants.HTTP_PROXY_PAC else if (it.getHost() != "") Constants.HTTP_PROXY_MANUAL else Constants.HTTP_PROXY_NONE }.orElse(Constants.HTTP_PROXY_NONE)
val keyPair = other.keyPair
privateKey = keyPair.privateKey.toBase64()
}
@@ -111,6 +165,20 @@ class InterfaceProxy : BaseObservable, Parcelable {
if (includedApplications.isNotEmpty()) builder.includeApplications(includedApplications)
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
+ if (Constants.HTTP_PROXY_MANUAL.equals(httpProxyMenu) && httpProxyHostname.isNotEmpty()) {
+ var httpProxy: String
+ if (httpProxyHostname.contains(":")) {
+ httpProxy = "[" + httpProxyHostname + "]"
+ } else {
+ httpProxy = httpProxyHostname
+ }
+ if (httpProxyPort.isNotEmpty()) {
+ httpProxy += ":" + httpProxyPort;
+ }
+ builder.parseHttpProxy(httpProxy)
+ } else if (Constants.HTTP_PROXY_PAC.equals(httpProxyMenu) && httpProxyPac.isNotEmpty()) {
+ builder.parseHttpProxy("pac:" + httpProxyPac)
+ }
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
return builder.build()
}
@@ -122,6 +190,10 @@ class InterfaceProxy : BaseObservable, Parcelable {
dest.writeStringList(includedApplications)
dest.writeString(listenPort)
dest.writeString(mtu)
+ dest.writeString(httpProxyMenu)
+ dest.writeString(httpProxyHostname)
+ dest.writeString(httpProxyPort)
+ dest.writeString(httpProxyPac)
dest.writeString(privateKey)
}
diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt
new file mode 100644
index 00000000..80b32fd5
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/PeerDetail.kt
@@ -0,0 +1,85 @@
+package com.wireguard.android.viewmodel
+
+import android.util.Log
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import androidx.databinding.Observable
+import androidx.databinding.ObservableList
+import androidx.databinding.ObservableArrayList
+
+import com.wireguard.android.BR
+import com.wireguard.config.InetEndpoint
+import com.wireguard.config.InetNetwork
+import com.wireguard.config.Peer
+import com.wireguard.crypto.Key;
+
+import java.util.Optional;
+
+import kotlin.collections.LinkedHashSet
+
+
+class PeerDetail : BaseObservable {
+ var peer: Peer?
+ private var owner: ConfigDetail? = null
+
+ @get:Bindable
+ var publicKey: Key
+
+ @get:Bindable
+ var allowedIps: ObservableList<InetNetwork> = ObservableArrayList<InetNetwork>()
+
+ @get:Bindable
+ var endpoint: Optional<InetEndpoint> = Optional.empty()
+ get() {
+ if (!field.isEmpty()) {
+ return field
+ } else {
+ return Optional.ofNullable(peer?.endpoint?.get())
+ }
+ }
+
+ set(value) {
+ Log.i(TAG, "notifyPropertyChanged endpoint " + this + ", " + value)
+ field = value
+ notifyPropertyChanged(BR.endpoint)
+ }
+
+ @get:Bindable
+ var persistentKeepalive: Optional<Int> = Optional.empty()
+
+ constructor(other: Peer) {
+ peer = other
+ publicKey = other.getPublicKey()
+ allowedIps.addAll(other.getAllowedIps())
+ endpoint = other.getEndpoint();
+ persistentKeepalive = other.getPersistentKeepalive()
+ }
+
+ constructor(publicKey: Key) {
+ peer = null
+ this.publicKey = publicKey
+ }
+
+ fun bind(owner: ConfigDetail) {
+ this.owner = owner
+ }
+
+ override fun addOnPropertyChangedCallback (callback: Observable.OnPropertyChangedCallback) {
+ Log.i(TAG, "addOnPropertyChangedCallback " + this + ", " + callback)
+ super.addOnPropertyChangedCallback(callback)
+ }
+
+ /**
+ * Converts the {@code Peer} into a string suitable for debugging purposes. The {@code Peer} is
+ * identified by its public key and (if known) its endpoint.
+ *
+ * @return a concise single-line identifier for the {@code Peer}
+ */
+ override fun toString(): String {
+ return "(Peer " + publicKey.toBase64() + ")"
+ }
+
+ companion object {
+ private const val TAG = "WireGuard/PeerDetail"
+ }
+}
diff --git a/ui/src/main/res/drawable/ic_action_add_white.xml b/ui/src/main/res/drawable/ic_action_add_white.xml
index 18fe19db..1ea440df 100644
--- a/ui/src/main/res/drawable/ic_action_add_white.xml
+++ b/ui/src/main/res/drawable/ic_action_add_white.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:tint="?attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_delete.xml b/ui/src/main/res/drawable/ic_action_delete.xml
index 51517c42..73eb7352 100644
--- a/ui/src/main/res/drawable/ic_action_delete.xml
+++ b/ui/src/main/res/drawable/ic_action_delete.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_edit.xml b/ui/src/main/res/drawable/ic_action_edit.xml
index 8d8acf85..d40c78d9 100644
--- a/ui/src/main/res/drawable/ic_action_edit.xml
+++ b/ui/src/main/res/drawable/ic_action_edit.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_edit_white.xml b/ui/src/main/res/drawable/ic_action_edit_white.xml
deleted file mode 100644
index 212fc72c..00000000
--- a/ui/src/main/res/drawable/ic_action_edit_white.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="?attr/colorOnSecondary"
- android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
-</vector>
diff --git a/ui/src/main/res/drawable/ic_action_generate.xml b/ui/src/main/res/drawable/ic_action_generate.xml
index 7324eae4..51d26aed 100644
--- a/ui/src/main/res/drawable/ic_action_generate.xml
+++ b/ui/src/main/res/drawable/ic_action_generate.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_open_white.xml b/ui/src/main/res/drawable/ic_action_open.xml
index 91075680..c9fd6fba 100644
--- a/ui/src/main/res/drawable/ic_action_open_white.xml
+++ b/ui/src/main/res/drawable/ic_action_open.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_save.xml b/ui/src/main/res/drawable/ic_action_save.xml
index ed98e85a..6e618edb 100644
--- a/ui/src/main/res/drawable/ic_action_save.xml
+++ b/ui/src/main/res/drawable/ic_action_save.xml
@@ -2,9 +2,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml b/ui/src/main/res/drawable/ic_action_scan_qr_code.xml
index a5ede695..4522ae46 100644
--- a/ui/src/main/res/drawable/ic_action_scan_qr_code_white.xml
+++ b/ui/src/main/res/drawable/ic_action_scan_qr_code.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnSecondary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_select_all.xml b/ui/src/main/res/drawable/ic_action_select_all.xml
index 43f5f15e..490ca715 100644
--- a/ui/src/main/res/drawable/ic_action_select_all.xml
+++ b/ui/src/main/res/drawable/ic_action_select_all.xml
@@ -2,9 +2,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?attr/colorOnPrimary"
+ android:fillColor="#FFFFFFFF"
android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_action_share_white.xml b/ui/src/main/res/drawable/ic_action_share_white.xml
index 70843cf3..04ee5b74 100644
--- a/ui/src/main/res/drawable/ic_action_share_white.xml
+++ b/ui/src/main/res/drawable/ic_action_share_white.xml
@@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
diff --git a/ui/src/main/res/drawable/ic_arrow_back.xml b/ui/src/main/res/drawable/ic_arrow_back.xml
index 0d549893..05d8f7d7 100644
--- a/ui/src/main/res/drawable/ic_arrow_back.xml
+++ b/ui/src/main/res/drawable/ic_arrow_back.xml
@@ -6,9 +6,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="#000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z" />
</vector>
diff --git a/ui/src/main/res/drawable/ic_settings.xml b/ui/src/main/res/drawable/ic_settings.xml
index 6d1cfa71..af9f2634 100644
--- a/ui/src/main/res/drawable/ic_settings.xml
+++ b/ui/src/main/res/drawable/ic_settings.xml
@@ -1,9 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+ android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="?android:attr/colorForeground"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
</vector>
diff --git a/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml b/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
index 68f186b6..9ed57ac6 100644
--- a/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
+++ b/ui/src/main/res/layout/add_tunnels_bottom_sheet.xml
@@ -20,7 +20,7 @@
android:text="@string/create_from_file"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_open_white"
+ app:icon="@drawable/ic_action_open"
app:iconPadding="@dimen/bottom_sheet_icon_padding"
app:iconTint="?attr/colorSecondary"
app:layout_constraintBottom_toTopOf="@+id/create_from_qrcode"
@@ -44,7 +44,7 @@
android:text="@string/create_from_qr_code"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnSurface"
- app:icon="@drawable/ic_action_scan_qr_code_white"
+ app:icon="@drawable/ic_action_scan_qr_code"
app:iconPadding="@dimen/bottom_sheet_icon_padding"
app:iconTint="?attr/colorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/create_empty"
diff --git a/ui/src/main/res/layout/http_proxy_menu_item.xml b/ui/src/main/res/layout/http_proxy_menu_item.xml
new file mode 100644
index 00000000..8ad5c026
--- /dev/null
+++ b/ui/src/main/res/layout/http_proxy_menu_item.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+/>
diff --git a/ui/src/main/res/layout/tunnel_detail_fragment.xml b/ui/src/main/res/layout/tunnel_detail_fragment.xml
index 8e34f082..59112d5e 100644
--- a/ui/src/main/res/layout/tunnel_detail_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_detail_fragment.xml
@@ -5,6 +5,8 @@
<data>
+ <import type="android.os.Build" />
+
<import type="com.wireguard.android.backend.Tunnel.State" />
<import type="com.wireguard.android.util.ClipboardUtils" />
@@ -19,7 +21,7 @@
<variable
name="config"
- type="com.wireguard.config.Config" />
+ type="com.wireguard.android.viewmodel.ConfigDetail" />
</data>
<ScrollView
@@ -117,7 +119,7 @@
android:nextFocusForward="@id/addresses_text"
android:onClick="@{ClipboardUtils::copyTextView}"
android:singleLine="true"
- android:text="@{config.interface.keyPair.publicKey.toBase64}"
+ android:text="@{config.config.interface.keyPair.publicKey.toBase64}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/public_key_label"
tools:text="wOs2eguFEohqIZxlSJ1CAT9584tc6ejj9hfGFsoBVkA=" />
@@ -129,7 +131,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/addresses_text"
android:text="@string/addresses"
- android:visibility="@{config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/public_key_text" />
@@ -140,13 +142,40 @@
android:layout_height="wrap_content"
android:contentDescription="@string/addresses"
android:nextFocusUp="@id/public_key_text"
+ android:nextFocusDown="@id/dhcp_addresses_text"
+ android:nextFocusForward="@id/dhcp_addresses_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:text="@{config.config.interface.addresses}"
+ android:visibility="@{config.config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/addresses_label"
+ tools:text="fc00:bbbb:bbbb:bb11::3:368b/128" />
+
+ <TextView
+ android:id="@+id/dhcp_addresses_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/dhcp_addresses_text"
+ android:text="@string/dhcp_addresses"
+ android:visibility="@{tunnel.dhcp == null ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/addresses_text" />
+
+ <TextView
+ android:id="@+id/dhcp_addresses_text"
+ style="@style/DetailText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/dhcp_addresses"
+ android:nextFocusUp="@id/addresses_text"
android:nextFocusDown="@id/dns_servers_text"
android:nextFocusForward="@id/dns_servers_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.addresses}"
- android:visibility="@{config.interface.addresses.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{tunnel.dhcp.addresses}"
+ android:visibility="@{tunnel.dhcp == null ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/addresses_label"
+ app:layout_constraintTop_toBottomOf="@+id/dhcp_addresses_label"
tools:text="fc00:bbbb:bbbb:bb11::3:368b/128" />
<TextView
@@ -156,9 +185,9 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_servers_text"
android:text="@string/dns_servers"
- android:visibility="@{config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/addresses_text" />
+ app:layout_constraintTop_toBottomOf="@id/dhcp_addresses_text" />
<TextView
android:id="@+id/dns_servers_text"
@@ -166,12 +195,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_servers"
- android:nextFocusUp="@id/addresses_text"
+ android:nextFocusUp="@id/dhcp_addresses_text"
android:nextFocusDown="@id/dns_search_domains_text"
android:nextFocusForward="@id/dns_search_domains_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.dnsServers}"
- android:visibility="@{config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.dnsServers}"
+ android:visibility="@{config.config.interface.dnsServers.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dns_servers_label"
tools:text="8.8.8.8, 8.8.4.4" />
@@ -183,7 +212,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_search_domain_text"
android:text="@string/dns_search_domains"
- android:visibility="@{config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{config.config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dns_servers_text" />
@@ -197,8 +226,8 @@
android:nextFocusDown="@id/listen_port_text"
android:nextFocusForward="@id/listen_port_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.dnsSearchDomains}"
- android:visibility="@{config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.dnsSearchDomains}"
+ android:visibility="@{config.config.interface.dnsSearchDomains.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dns_search_domains_label"
tools:text="zx2c4.com" />
@@ -210,7 +239,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/listen_port_text"
android:text="@string/listen_port"
- android:visibility="@{!config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!config.config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toStartOf="@id/mtu_label"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toStartOf="parent"
@@ -224,11 +253,11 @@
android:contentDescription="@string/listen_port"
android:nextFocusRight="@id/mtu_text"
android:nextFocusUp="@id/dns_search_domains_text"
- android:nextFocusDown="@id/applications_text"
+ android:nextFocusDown="@id/http_proxy_text"
android:nextFocusForward="@id/mtu_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.listenPort}"
- android:visibility="@{!config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.listenPort}"
+ android:visibility="@{!config.config.interface.listenPort.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toStartOf="@id/mtu_label"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toStartOf="parent"
@@ -242,7 +271,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/mtu_text"
android:text="@string/mtu"
- android:visibility="@{!config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!config.config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintLeft_toRightOf="@id/listen_port_label"
@@ -257,10 +286,10 @@
android:contentDescription="@string/mtu"
android:nextFocusLeft="@id/listen_port_text"
android:nextFocusUp="@id/dns_servers_text"
- android:nextFocusForward="@id/applications_text"
+ android:nextFocusForward="@id/http_proxy_text"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.mtu}"
- android:visibility="@{!config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:text="@{config.config.interface.mtu}"
+ android:visibility="@{!config.config.interface.mtu.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toEndOf="@id/listen_port_label"
@@ -268,6 +297,40 @@
app:layout_constraintTop_toBottomOf="@+id/mtu_label"
tools:text="1500" />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/listen_port_mtu_barrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="listen_port_text,mtu_text" />
+
+ <TextView
+ android:id="@+id/http_proxy_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/http_proxy_text"
+ android:text="@string/http_proxy"
+ android:visibility="@{(Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.Q || !config.config.interface.httpProxy.isPresent()) ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/listen_port_mtu_barrier" />
+
+ <TextView
+ android:id="@+id/http_proxy_text"
+ style="@style/DetailText"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/http_proxy"
+ android:nextFocusUp="@id/listen_port_text"
+ android:nextFocusDown="@id/applications_text"
+ android:nextFocusForward="@id/applications_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:text="@{config.config.interface.httpProxy}"
+ android:visibility="@{(Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.Q || !config.config.interface.httpProxy.isPresent()) ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_label"
+ app:layout_constraintStart_toStartOf="parent"
+ tools:text="http://example.com:8888" />
+
<TextView
android:id="@+id/applications_label"
android:layout_width="match_parent"
@@ -275,9 +338,9 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/applications_text"
android:text="@string/applications"
- android:visibility="@{config.interface.includedApplications.isEmpty() &amp;&amp; config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/listen_port_text" />
+ android:visibility="@{config.config.interface.includedApplications.isEmpty() &amp;&amp; config.config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ app:layout_constraintTop_toBottomOf="@+id/http_proxy_text"
+ app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/applications_text"
@@ -289,10 +352,10 @@
android:nextFocusDown="@id/peers_layout"
android:nextFocusForward="@id/peers_layout"
android:onClick="@{ClipboardUtils::copyTextView}"
- android:text="@{config.interface.includedApplications.isEmpty() ? @plurals/n_excluded_applications(config.interface.excludedApplications.size(), config.interface.excludedApplications.size()) : @plurals/n_included_applications(config.interface.includedApplications.size(), config.interface.includedApplications.size())}"
- android:visibility="@{config.interface.includedApplications.isEmpty() &amp;&amp; config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
- app:layout_constraintStart_toStartOf="parent"
+ android:text="@{config.config.interface.includedApplications.isEmpty() ? @plurals/n_excluded_applications(config.config.interface.excludedApplications.size(), config.config.interface.excludedApplications.size()) : @plurals/n_included_applications(config.config.interface.includedApplications.size(), config.config.interface.includedApplications.size())}"
+ android:visibility="@{config.config.interface.includedApplications.isEmpty() &amp;&amp; config.config.interface.excludedApplications.isEmpty() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@+id/applications_label"
+ app:layout_constraintStart_toStartOf="parent"
tools:text="8 excluded" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
@@ -304,6 +367,7 @@
android:layout_marginTop="8dp"
android:divider="@null"
android:orientation="vertical"
+ app:fragment="@{fragment}"
app:items="@{config.peers}"
app:layout="@{@layout/tunnel_detail_peer}"
app:layout_constraintStart_toStartOf="parent"
diff --git a/ui/src/main/res/layout/tunnel_detail_peer.xml b/ui/src/main/res/layout/tunnel_detail_peer.xml
index 0fbee8f1..b4a07003 100644
--- a/ui/src/main/res/layout/tunnel_detail_peer.xml
+++ b/ui/src/main/res/layout/tunnel_detail_peer.xml
@@ -9,7 +9,7 @@
<variable
name="item"
- type="com.wireguard.config.Peer" />
+ type="com.wireguard.android.viewmodel.PeerDetail" />
</data>
<com.google.android.material.card.MaterialCardView
@@ -64,7 +64,7 @@
android:layout_marginTop="8dp"
android:labelFor="@+id/pre_shared_key_text"
android:text="@string/pre_shared_key"
- android:visibility="@{!item.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!item.peer.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/public_key_text" />
@@ -81,7 +81,7 @@
android:nextFocusForward="@id/allowed_ips_text"
android:singleLine="true"
android:text="@string/pre_shared_key_enabled"
- android:visibility="@{!item.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
+ android:visibility="@{!item.peer.preSharedKey.isPresent() ? android.view.View.GONE : android.view.View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pre_shared_key_label"
tools:text="8VyS8W8XeMcBWfKp1GuG3/fZlnUQFkqMNbrdmZtVQIM=" />
@@ -194,6 +194,34 @@
app:layout_constraintTop_toBottomOf="@+id/transfer_label"
tools:text="1024 MB"
tools:visibility="visible" />
+
+ <TextView
+ android:id="@+id/last_handshake_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/persistent_keepalive_text"
+ android:layout_marginTop="8dp"
+ android:labelFor="@+id/last_handshake_text"
+ android:text="@string/last_handshake"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/transfer_text"
+ tools:visibility="visible" />
+
+ <TextView
+ android:id="@+id/last_handshake_text"
+ style="@style/DetailText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/last_handshake_label"
+ android:contentDescription="@string/last_handshake"
+ android:nextFocusUp="@id/transfer_text"
+ android:onClick="@{ClipboardUtils::copyTextView}"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/last_handshake_label"
+ tools:text="29 seconds ago"
+ tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
diff --git a/ui/src/main/res/layout/tunnel_editor_fragment.xml b/ui/src/main/res/layout/tunnel_editor_fragment.xml
index 59572b32..789c839b 100644
--- a/ui/src/main/res/layout/tunnel_editor_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_editor_fragment.xml
@@ -5,6 +5,8 @@
<data>
+ <import type="android.os.Build" />
+
<import type="com.wireguard.android.util.ClipboardUtils" />
<import type="com.wireguard.android.widget.KeyInputFilter" />
@@ -210,7 +212,7 @@
android:imeOptions="actionNext"
android:inputType="textNoSuggestions|textVisiblePassword"
android:nextFocusUp="@id/addresses_label_text"
- android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusDown="@id/http_proxy_hostname_text"
android:nextFocusForward="@id/mtu_text"
android:text="@={config.interface.dnsServers}" />
</com.google.android.material.textfield.TextInputLayout>
@@ -235,19 +237,112 @@
android:imeOptions="actionDone"
android:inputType="number"
android:nextFocusUp="@id/listen_port_text"
- android:nextFocusDown="@id/set_excluded_applications"
- android:nextFocusForward="@id/set_excluded_applications"
+ android:nextFocusDown="@id/http_proxy_hostname_text"
+ android:nextFocusForward="@id/http_proxy_hostname_text"
android:text="@={config.interface.mtu}"
android:textAlignment="center" />
</com.google.android.material.textfield.TextInputLayout>
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_menu"
+ style="@style/ExposedDropDownMenu"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy"
+ app:expandedHintEnabled="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dns_servers_label_layout">
+
+ <com.google.android.material.textfield.MaterialAutoCompleteTextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="none"
+ android:text="@={config.interface.httpProxyMenu}"
+ />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_hostname_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_hostname"
+ android:visibility="@{config.interface.httpProxyManualVisibility}"
+ app:layout_constraintEnd_toStartOf="@id/http_proxy_port_label_layout"
+ app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintHorizontal_weight="0.7"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_menu">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_hostname_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionNext"
+ android:inputType="textNoSuggestions|textVisiblePassword"
+ android:nextFocusUp="@id/mtu_text"
+ android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusForward="@id/http_proxy_port_text"
+ android:text="@={config.interface.httpProxyHostname}" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_port_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_port"
+ android:visibility="@{config.interface.httpProxyManualVisibility}"
+ app:expandedHintEnabled="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_weight="0.3"
+ app:layout_constraintStart_toEndOf="@id/http_proxy_hostname_label_layout"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_menu">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_port_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionDone"
+ android:nextFocusUp="@id/mtu_text"
+ android:nextFocusDown="@id/http_proxy_pac_label_layout"
+ android:nextFocusForward="@id/http_proxy_pac_label_layout"
+ android:text="@={config.interface.httpProxyPort}"
+ android:textAlignment="center" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/http_proxy_pac_label_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:hint="@string/http_proxy_pac"
+ android:visibility="@{config.interface.httpProxyPacVisibility}"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_hostname_label_layout">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/http_proxy_pac_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionNext"
+ android:inputType="textNoSuggestions|textVisiblePassword"
+ android:nextFocusUp="@id/http_proxy_hostname_text"
+ android:nextFocusDown="@id/set_excluded_applications"
+ android:nextFocusForward="@id/set_excluded_applications"
+ android:text="@={config.interface.httpProxyPac}" />
+ </com.google.android.material.textfield.TextInputLayout>
+
<com.google.android.material.button.MaterialButton
android:id="@+id/set_excluded_applications"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- android:nextFocusUp="@id/dns_servers_text"
+ android:nextFocusUp="@id/http_proxy_hostname_text"
android:nextFocusDown="@id/peers_layout"
android:nextFocusForward="@id/peers_layout"
android:onClick="@{fragment::onRequestSetExcludedIncludedApplications}"
@@ -256,7 +351,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/mtu_label_layout"
+ app:layout_constraintTop_toBottomOf="@id/http_proxy_pac_label_layout"
app:rippleColor="?attr/colorSecondary"
tools:text="4 excluded applications" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
index 6c090199..320b29e2 100644
--- a/ui/src/main/res/values/strings.xml
+++ b/ui/src/main/res/values/strings.xml
@@ -63,13 +63,14 @@
<string name="allow_remote_control_intents_summary_on">External apps may toggle tunnels (advanced)</string>
<string name="allow_remote_control_intents_title">Allow remote control apps</string>
<string name="allowed_ips">Allowed IPs</string>
- <string name="app_name" translatable="false">WireGuard</string>
+ <string name="app_name" translatable="false">(m7n.eu) WireGuard</string>
<string name="bad_config_context">%1$s\'s %2$s</string>
<string name="bad_config_context_top_level">%s</string>
<string name="bad_config_error">%1$s in %2$s</string>
<string name="bad_config_explanation_pka">: Must be positive and no more than 65535</string>
<string name="bad_config_explanation_positive_number">: Must be positive</string>
<string name="bad_config_explanation_udp_port">: Must be a valid UDP port number</string>
+ <string name="bad_config_explanation_http_proxy">: Must be valid proxy hostname and port</string>
<string name="bad_config_reason_invalid_key">Invalid key</string>
<string name="bad_config_reason_invalid_number">Invalid number</string>
<string name="bad_config_reason_invalid_value">Invalid value</string>
@@ -104,6 +105,7 @@
<string name="dark_theme_summary_on">Currently using dark (night) theme</string>
<string name="dark_theme_title">Use dark theme</string>
<string name="delete">Delete</string>
+ <string name="dhcp_addresses">DHCP Addresses</string>
<string name="tv_delete">Select tunnel to delete</string>
<string name="tv_select_a_storage_drive">Select a storage drive</string>
<string name="tv_no_file_picker">Please install a file management utility to browse files</string>
@@ -112,6 +114,10 @@
<string name="disable_config_export_description">Disabling config exporting makes private keys less accessible</string>
<string name="dns_servers">DNS servers</string>
<string name="dns_search_domains">Search domains</string>
+ <string name="duration_days">%d days</string>
+ <string name="duration_hours">%d hours</string>
+ <string name="duration_minutes">%d minutes</string>
+ <string name="duration_seconds">%d seconds</string>
<string name="edit">Edit</string>
<string name="endpoint">Endpoint</string>
<string name="error_down">Error bringing down tunnel: %s</string>
@@ -126,6 +132,10 @@
<string name="hint_optional">(optional)</string>
<string name="hint_optional_discouraged">(optional, not recommended)</string>
<string name="hint_random">(random)</string>
+ <string name="http_proxy">Proxy</string>
+ <string name="http_proxy_hostname">Proxy hostname</string>
+ <string name="http_proxy_pac">Proxy Auto-Config URL</string>
+ <string name="http_proxy_port">Proxy port</string>
<string name="illegal_filename_error">Illegal file name ā€œ%sā€</string>
<string name="import_error">Unable to import tunnel: %s</string>
<string name="import_from_qr_code">Import Tunnel from QR Code</string>
@@ -136,6 +146,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 keys must be 44 characters (32 bytes)</string>
<string name="key_length_explanation_binary">: WireGuard keys must be 32 bytes</string>
<string name="key_length_explanation_hex">: WireGuard hex keys must be 64 characters (32 bytes)</string>
+ <string name="last_handshake">Latest handshake</string>
+ <string name="last_handshake_ago">%s ago</string>
<string name="listen_port">Listen port</string>
<string name="log_export_error">Unable to export log: %s</string>
<string name="log_export_subject">WireGuard Android Log File</string>
diff --git a/ui/src/main/res/values/styles.xml b/ui/src/main/res/values/styles.xml
index b90f111f..da3c2cb7 100644
--- a/ui/src/main/res/values/styles.xml
+++ b/ui/src/main/res/values/styles.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="WireGuardTheme" parent="Theme.MaterialComponents.DayNight">
+ <style name="WireGuardTheme" parent="Theme.Material3.DayNight">
<item name="colorPrimary">@color/primary_color</item>
<item name="colorOnPrimary">@color/color_control_normal</item>
<item name="colorPrimaryDark">@color/primary_color</item>
@@ -17,9 +17,6 @@
<item name="elevationOverlayEnabled">true</item>
<item name="android:statusBarColor">@color/status_bar_color</item>
<item name="android:windowBackground">@color/primary_color</item>
- <item name="alertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="textInputStyle">@style/TextInputLayoutBase</item>
<item name="materialCardViewStyle">@style/AppTheme.MaterialCardView</item>
</style>
@@ -32,12 +29,6 @@
<item name="cardBackgroundColor">?attr/elevationOverlayColor</item>
</style>
- <style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
- <item name="colorPrimary">@color/secondary_color</item>
- <item name="colorSecondary">@color/secondary_color</item>
- <item name="android:windowBackground">?attr/colorBackground</item>
- </style>
-
<style name="BottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:navigationBarColor">?attr/colorBackground</item>
@@ -71,11 +62,7 @@
<item name="colorControlActivated">@color/color_control_normal</item>
</style>
- <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
+ <style name="ExposedDropDownMenu" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<item name="boxStrokeColor">?attr/colorSecondary</item>
- <item name="hintTextColor">?attr/colorOnPrimary</item>
- <item name="materialThemeOverlay">
- @style/ThemeOverlay.AppTheme.TextInputEditText.OutlinedBox
- </item>
</style>
</resources>
diff --git a/ui/src/main/res/values/tv_styles.xml b/ui/src/main/res/values/tv_styles.xml
index c5477f6a..536ca752 100644
--- a/ui/src/main/res/values/tv_styles.xml
+++ b/ui/src/main/res/values/tv_styles.xml
@@ -16,12 +16,26 @@
<item name="elevationOverlayEnabled">false</item>
<item name="android:statusBarColor">@color/tv_primary_color</item>
<item name="android:windowBackground">@color/tv_primary_color</item>
- <item name="alertDialogTheme">@style/AppTheme.Dialog</item>
- <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item>
+ <item name="alertDialogTheme">@style/TvTheme.Dialog</item>
+ <item name="materialAlertDialogTheme">@style/TvTheme.Dialog</item>
<item name="textInputStyle">@style/TextInputLayoutBase</item>
<item name="materialCardViewStyle">@style/TvTheme.MaterialCardView</item>
</style>
+ <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
+ <item name="boxStrokeColor">?attr/colorSecondary</item>
+ <item name="hintTextColor">?attr/colorOnPrimary</item>
+ <item name="materialThemeOverlay">
+ @style/ThemeOverlay.AppTheme.TextInputEditText.OutlinedBox
+ </item>
+ </style>
+
+ <style name="TvTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
+ <item name="colorPrimary">@color/secondary_color</item>
+ <item name="colorSecondary">@color/secondary_color</item>
+ <item name="android:windowBackground">?attr/colorBackground</item>
+ </style>
+
<style name="TvTheme.MaterialCardView" parent="Widget.MaterialComponents.CardView">
<item name="cornerRadius">4dp</item>
<item name="cardElevation">8dp</item>