aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/main/java/com/wireguard
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-04-05 21:37:45 -0600
committerJason A. Donenfeld <Jason@zx2c4.com>2020-04-06 02:40:49 -0600
commit7db0fa915ef909ad33b9b29754e1fd1e7ed260a9 (patch)
tree77742b284864e2d875fc8b4be56842282e0e2746 /ui/src/main/java/com/wireguard
parenttunnel: support IncludedApplications as whitelist (diff)
downloadwireguard-android-7db0fa915ef909ad33b9b29754e1fd1e7ed260a9.tar.xz
wireguard-android-7db0fa915ef909ad33b9b29754e1fd1e7ed260a9.zip
AppListDialogFragment: support both inclusion and exclusion
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/src/main/java/com/wireguard')
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt77
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt35
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ApplicationData.kt6
-rw-r--r--ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt7
4 files changed, 89 insertions, 36 deletions
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 8bf0cf14..4fdebd16 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -5,14 +5,17 @@
package com.wireguard.android.fragment
import android.app.Dialog
-import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
+import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
+import androidx.databinding.Observable
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
+import com.google.android.material.tabs.TabLayout
import com.wireguard.android.Application
+import com.wireguard.android.BR
import com.wireguard.android.R
import com.wireguard.android.databinding.AppListDialogFragmentBinding
import com.wireguard.android.databinding.ObservableKeyedArrayList
@@ -21,7 +24,10 @@ import com.wireguard.android.util.ErrorMessages
class AppListDialogFragment : DialogFragment() {
private val appData: ObservableKeyedArrayList<String, ApplicationData> = ObservableKeyedArrayList()
- private var currentlyExcludedApps = emptyList<String>()
+ private var currentlySelectedApps = emptyList<String>()
+ private var initiallyExcluded: Boolean = false
+ private var button: Button? = null
+ private var tabs: TabLayout? = null
private fun loadData() {
val activity = activity ?: return
@@ -33,7 +39,14 @@ class AppListDialogFragment : DialogFragment() {
val applicationData: MutableList<ApplicationData> = ArrayList()
resolveInfos.forEach {
val packageName = it.activityInfo.packageName
- applicationData.add(ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)))
+ val appData = ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
+ applicationData.add(appData)
+ appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
+ if (propertyId == BR.selected)
+ setButtonText()
+ }
+ })
}
applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
applicationData
@@ -52,17 +65,34 @@ class AppListDialogFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val excludedApps = requireArguments().getStringArrayList(KEY_EXCLUDED_APPS)
- currentlyExcludedApps = (excludedApps ?: emptyList())
+ currentlySelectedApps = (arguments?.getStringArrayList(KEY_SELECTED_APPS) ?: emptyList())
+ initiallyExcluded = arguments?.getBoolean(KEY_IS_EXCLUDED) ?: true
+ }
+
+ private fun setButtonText() {
+ val numSelected = appData.count { it.isSelected }
+ button?.text = if (numSelected == 0)
+ getString(R.string.use_all_applications)
+ else when (tabs?.selectedTabPosition) {
+ 0 -> resources.getQuantityString(R.plurals.exclude_n_applications, numSelected, numSelected)
+ 1 -> resources.getQuantityString(R.plurals.include_n_applications, numSelected, numSelected)
+ else -> null
+ }
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
- alertDialogBuilder.setTitle(R.string.excluded_applications)
val binding = AppListDialogFragmentBinding.inflate(requireActivity().layoutInflater, null, false)
binding.executePendingBindings()
alertDialogBuilder.setView(binding.root)
- alertDialogBuilder.setPositiveButton(R.string.set_exclusions) { _, _ -> setExclusionsAndDismiss() }
+ tabs = binding.tabs
+ tabs!!.selectTab(binding.tabs.getTabAt(if (initiallyExcluded) 0 else 1))
+ tabs!!.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+ override fun onTabReselected(tab: TabLayout.Tab?) = Unit
+ override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
+ override fun onTabSelected(tab: TabLayout.Tab?) = setButtonText()
+ })
+ alertDialogBuilder.setPositiveButton(" ") { _, _ -> setSelectionAndDismiss() }
alertDialogBuilder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
alertDialogBuilder.setNeutralButton(R.string.toggle_all) { _, _ -> }
binding.fragment = this
@@ -70,39 +100,40 @@ class AppListDialogFragment : DialogFragment() {
loadData()
val dialog = alertDialogBuilder.create()
dialog.setOnShowListener {
- dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener {
- val selectedItems = appData
- .filter { it.isExcludedFromTunnel }
-
- val excludeAll = selectedItems.isEmpty()
+ button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
+ setButtonText()
+ dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { _ ->
+ val selectAll = appData.none { it.isSelected }
appData.forEach {
- it.isExcludedFromTunnel = excludeAll
+ it.isSelected = selectAll
}
}
}
return dialog
}
- private fun setExclusionsAndDismiss() {
- val excludedApps: MutableList<String> = ArrayList()
+ private fun setSelectionAndDismiss() {
+ val selectedApps: MutableList<String> = ArrayList()
for (data in appData) {
- if (data.isExcludedFromTunnel) {
- excludedApps.add(data.packageName)
+ if (data.isSelected) {
+ selectedApps.add(data.packageName)
}
}
- (targetFragment as AppExclusionListener?)!!.onExcludedAppsSelected(excludedApps)
+ (targetFragment as AppSelectionListener?)!!.onSelectedAppsSelected(selectedApps, tabs?.selectedTabPosition == 0)
dismiss()
}
- interface AppExclusionListener {
- fun onExcludedAppsSelected(excludedApps: List<String>)
+ interface AppSelectionListener {
+ fun onSelectedAppsSelected(selectedApps: List<String>, isExcluded: Boolean)
}
companion object {
- private const val KEY_EXCLUDED_APPS = "excludedApps"
- fun <T> newInstance(excludedApps: ArrayList<String?>?, target: T): AppListDialogFragment where T : Fragment?, T : AppExclusionListener? {
+ private const val KEY_SELECTED_APPS = "selected_apps"
+ private const val KEY_IS_EXCLUDED = "is_excluded"
+ fun <T> newInstance(selectedApps: ArrayList<String?>?, isExcluded: Boolean, target: T): AppListDialogFragment where T : Fragment?, T : AppSelectionListener? {
val extras = Bundle()
- extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps)
+ extras.putStringArrayList(KEY_SELECTED_APPS, selectedApps)
+ extras.putBoolean(KEY_IS_EXCLUDED, isExcluded)
val fragment = AppListDialogFragment()
fragment.setTargetFragment(target, 0)
fragment.arguments = extras
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 2e1e4531..dc1b8aa2 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
@@ -23,7 +23,7 @@ import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
-import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener
+import com.wireguard.android.fragment.AppListDialogFragment.AppSelectionListener
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.BiometricAuthenticator
import com.wireguard.android.util.ErrorMessages
@@ -35,7 +35,7 @@ import com.wireguard.config.Config
/**
* Fragment for editing a WireGuard configuration.
*/
-class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
+class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
private var haveShownKeys = false
private var binding: TunnelEditorFragmentBinding? = null
private var tunnel: ObservableTunnel? = null
@@ -88,11 +88,20 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
super.onDestroyView()
}
- override fun onExcludedAppsSelected(excludedApps: List<String>) {
- requireNotNull(binding) { "Tried to set excluded apps while no view was loaded" }
- binding!!.config!!.`interface`.excludedApplications.apply {
- clear()
- addAll(excludedApps)
+ override fun onSelectedAppsSelected(selectedApps: List<String>, isExcluded: Boolean) {
+ requireNotNull(binding) { "Tried to set excluded/included apps while no view was loaded" }
+ if (isExcluded) {
+ binding!!.config!!.`interface`.includedApplications.clear()
+ binding!!.config!!.`interface`.excludedApplications.apply {
+ clear()
+ addAll(selectedApps)
+ }
+ } else {
+ binding!!.config!!.`interface`.excludedApplications.clear()
+ binding!!.config!!.`interface`.includedApplications.apply {
+ clear()
+ addAll(selectedApps)
+ }
}
}
@@ -150,10 +159,16 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
}
@Suppress("UNUSED_PARAMETER")
- fun onRequestSetExcludedApplications(view: View?) {
+ fun onRequestSetExcludedIncludedApplications(view: View?) {
if (binding != null) {
- val excludedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications)
- val fragment = AppListDialogFragment.newInstance(excludedApps, this)
+ var isExcluded = true
+ var selectedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications)
+ if (selectedApps.isEmpty()) {
+ selectedApps = ArrayList(binding!!.config!!.`interface`.includedApplications)
+ if (selectedApps.isNotEmpty())
+ isExcluded = false
+ }
+ val fragment = AppListDialogFragment.newInstance(selectedApps, isExcluded, this)
fragment.show(parentFragmentManager, null)
}
}
diff --git a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
index e931cdd2..e0961f04 100644
--- a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
@@ -10,13 +10,13 @@ import androidx.databinding.Bindable
import com.wireguard.android.BR
import com.wireguard.android.databinding.Keyed
-class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isExcludedFromTunnel: Boolean) : BaseObservable(), Keyed<String> {
+class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isSelected: Boolean) : BaseObservable(), Keyed<String> {
override val key = name
@get:Bindable
- var isExcludedFromTunnel = isExcludedFromTunnel
+ var isSelected = isSelected
set(value) {
field = value
- notifyPropertyChanged(BR.excludedFromTunnel)
+ notifyPropertyChanged(BR.selected)
}
}
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 53f2fa24..bd2a9831 100644
--- a/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
+++ b/ui/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.kt
@@ -23,6 +23,9 @@ class InterfaceProxy : BaseObservable, Parcelable {
val excludedApplications: ObservableList<String> = ObservableArrayList()
@get:Bindable
+ val includedApplications: ObservableList<String> = ObservableArrayList()
+
+ @get:Bindable
var addresses: String = ""
set(value) {
field = value
@@ -70,6 +73,7 @@ class InterfaceProxy : BaseObservable, Parcelable {
addresses = parcel.readString() ?: ""
dnsServers = parcel.readString() ?: ""
parcel.readStringList(excludedApplications)
+ parcel.readStringList(includedApplications)
listenPort = parcel.readString() ?: ""
mtu = parcel.readString() ?: ""
privateKey = parcel.readString() ?: ""
@@ -80,6 +84,7 @@ class InterfaceProxy : BaseObservable, Parcelable {
val dnsServerStrings = other.dnsServers.map { it.hostAddress }
dnsServers = Attribute.join(dnsServerStrings)
excludedApplications.addAll(other.excludedApplications)
+ includedApplications.addAll(other.includedApplications)
listenPort = other.listenPort.map { it.toString() }.orElse("")
mtu = other.mtu.map { it.toString() }.orElse("")
val keyPair = other.keyPair
@@ -103,6 +108,7 @@ class InterfaceProxy : BaseObservable, Parcelable {
if (addresses.isNotEmpty()) builder.parseAddresses(addresses)
if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers)
if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications)
+ if (includedApplications.isNotEmpty()) builder.includeApplications(includedApplications)
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort)
if (mtu.isNotEmpty()) builder.parseMtu(mtu)
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey)
@@ -113,6 +119,7 @@ class InterfaceProxy : BaseObservable, Parcelable {
dest.writeString(addresses)
dest.writeString(dnsServers)
dest.writeStringList(excludedApplications)
+ dest.writeStringList(includedApplications)
dest.writeString(listenPort)
dest.writeString(mtu)
dest.writeString(privateKey)