summaryrefslogtreecommitdiff
path: root/main/src/ui/java/de/blinkt/openvpn/fragments
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2020-05-06 17:42:12 +0200
committerArne Schwabe <arne@rfc2549.org>2020-05-06 17:42:24 +0200
commit72ae846ea9ce4c50441bdc347d86a39231176f3e (patch)
tree2bfd6966f6202bdbd6848ab46da0015d1daddd33 /main/src/ui/java/de/blinkt/openvpn/fragments
parent6dd6c760fed01f7bf2648362f1f893dc75dcf42d (diff)
Convert ListView in allowed apps to RecyclerView
Closes #693
Diffstat (limited to 'main/src/ui/java/de/blinkt/openvpn/fragments')
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/PackageAdapter.kt237
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.kt272
2 files changed, 262 insertions, 247 deletions
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/PackageAdapter.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/PackageAdapter.kt
new file mode 100644
index 00000000..29fcffc7
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/PackageAdapter.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2012-2020 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import androidx.recyclerview.widget.RecyclerView
+import de.blinkt.openvpn.R
+import de.blinkt.openvpn.VpnProfile
+import org.jetbrains.anko.runOnUiThread
+import java.util.*
+
+internal class AppViewHolder(var rootView : View) : RecyclerView.ViewHolder(rootView) {
+ var mInfo: ApplicationInfo? = null
+ lateinit var appName: TextView
+ lateinit var appIcon: ImageView
+ //public TextView appSize;
+ //public TextView disabled;
+ lateinit var checkBox: CompoundButton
+
+ companion object {
+
+ fun create(inflater: LayoutInflater, parent: ViewGroup): AppViewHolder {
+ val view = inflater.inflate(R.layout.allowed_application_layout, parent, false)
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ val holder = AppViewHolder(view)
+ holder.appName = view.findViewById<View>(R.id.app_name) as TextView
+ holder.appIcon = view.findViewById<View>(R.id.app_icon) as ImageView
+ //holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
+ //holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
+ holder.checkBox = view.findViewById<View>(R.id.app_selected) as CompoundButton
+ view.tag = holder
+
+
+ return holder
+ }
+
+ fun createSettingsHolder(inflater: LayoutInflater, parent: ViewGroup): AppViewHolder {
+
+ val settingsView = inflater.inflate(R.layout.allowed_application_settings, parent, false)
+
+ val holder = AppViewHolder(settingsView)
+ settingsView.tag = holder
+ return holder
+ }
+ }
+
+}
+
+internal class PackageAdapter(c: Context, vp: VpnProfile) : RecyclerView.Adapter<AppViewHolder>(), Filterable, CompoundButton.OnCheckedChangeListener {
+ private val mInflater: LayoutInflater = LayoutInflater.from(c)
+ private val mPm: PackageManager = c.packageManager
+ private var mPackages: Vector<ApplicationInfo> = Vector()
+ private val mFilter = ItemFilter()
+ private var mFilteredData: Vector<ApplicationInfo> = mPackages
+ private val mProfile = vp
+
+ init {
+ setHasStableIds(true)
+ }
+
+
+ fun populateList(c: Context) {
+ val installedPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA)
+
+ // Remove apps not using Internet
+
+ var androidSystemUid = 0
+ val apps = Vector<ApplicationInfo>()
+
+ try {
+ val system = mPm.getApplicationInfo("android", PackageManager.GET_META_DATA)
+ androidSystemUid = system.uid
+ apps.add(system)
+ } catch (e: PackageManager.NameNotFoundException) {
+ }
+
+
+ for (app in installedPackages) {
+
+ if (mPm.checkPermission(Manifest.permission.INTERNET, app.packageName) == PackageManager.PERMISSION_GRANTED && app.uid != androidSystemUid) {
+
+ apps.add(app)
+ }
+ }
+
+ Collections.sort(apps, ApplicationInfo.DisplayNameComparator(mPm))
+ mPackages = apps
+ mFilteredData = apps
+ c.runOnUiThread { notifyDataSetChanged() }
+ }
+
+ override fun getItemId(position: Int): Long {
+ if (position == 0)
+ return "settings".hashCode().toLong()
+ return mFilteredData[position - 1].packageName.hashCode().toLong()
+ }
+
+ override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
+ if (position == 0) {
+ bindSettingsView(holder)
+ } else
+ bindViewApp(position - 1, holder)
+
+ }
+ internal fun changeDisallowText(allowTextView:TextView, selectedAreDisallowed: Boolean) {
+ if (selectedAreDisallowed)
+ allowTextView.setText(R.string.vpn_disallow_radio)
+ else
+ allowTextView.setText(R.string.vpn_allow_radio)
+ }
+
+ private fun bindSettingsView(holder: AppViewHolder) {
+ val settingsView = holder.rootView
+ val allowTextView = settingsView.findViewById<View>(R.id.default_allow_text) as TextView
+
+ val vpnOnDefaultSwitch = settingsView.findViewById<View>(R.id.default_allow) as Switch
+
+ changeDisallowText(allowTextView, mProfile.mAllowedAppsVpnAreDisallowed)
+ vpnOnDefaultSwitch.isChecked = mProfile.mAllowedAppsVpnAreDisallowed
+ vpnOnDefaultSwitch.setOnCheckedChangeListener { _, isChecked ->
+ mProfile.mAllowedAppsVpnAreDisallowed = isChecked
+ notifyDataSetChanged()
+ }
+
+
+
+ val vpnAllowBypassSwitch = settingsView.findViewById<View>(R.id.allow_bypass) as Switch
+
+ vpnAllowBypassSwitch.setOnCheckedChangeListener { _, isChecked -> mProfile.mAllowAppVpnBypass = isChecked }
+
+ vpnAllowBypassSwitch.isChecked = mProfile.mAllowAppVpnBypass
+ }
+
+ fun bindViewApp(position: Int, viewHolder: AppViewHolder){
+ viewHolder.mInfo = mFilteredData[position]
+ val mInfo = mFilteredData[position]
+
+
+ var appName = mInfo.loadLabel(mPm)
+
+ if (TextUtils.isEmpty(appName))
+ appName = mInfo.packageName
+ viewHolder.appName.text = appName
+ viewHolder.appIcon.setImageDrawable(mInfo.loadIcon(mPm))
+ viewHolder.checkBox.tag = mInfo.packageName
+ viewHolder.checkBox.setOnCheckedChangeListener(this)
+
+
+ viewHolder.checkBox.isChecked = mProfile.mAllowedAppsVpn.contains(mInfo.packageName)
+ }
+
+
+ override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
+ val packageName = buttonView.tag as String
+ if (isChecked) {
+ mProfile.mAllowedAppsVpn.add(packageName)
+ } else {
+ mProfile.mAllowedAppsVpn.remove(packageName)
+ }
+ }
+
+ override fun getFilter(): Filter {
+ return mFilter
+ }
+
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
+ if (viewType == 0)
+ return AppViewHolder.createSettingsHolder(mInflater, parent)
+ else
+ return AppViewHolder.create(mInflater, parent)
+ }
+
+ override fun getItemCount(): Int {
+ return mFilteredData.size + 1
+
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (position == 0) 0 else 1
+ }
+
+ private inner class ItemFilter : Filter() {
+ override fun performFiltering(constraint: CharSequence): FilterResults {
+
+ val filterString = constraint.toString().toLowerCase(Locale.getDefault())
+
+ val results = FilterResults()
+
+
+ val count = mPackages.size
+ val nlist = Vector<ApplicationInfo>(count)
+
+ for (i in 0 until count) {
+ val pInfo = mPackages[i]
+ var appName = pInfo.loadLabel(mPm)
+
+ if (TextUtils.isEmpty(appName))
+ appName = pInfo.packageName
+
+ if (appName is String) {
+ if (appName.toLowerCase(Locale.getDefault()).contains(filterString))
+ nlist.add(pInfo)
+ } else {
+ if (appName.toString().toLowerCase(Locale.getDefault()).contains(filterString))
+ nlist.add(pInfo)
+ }
+ }
+ results.values = nlist
+ results.count = nlist.size
+
+ return results
+ }
+
+ override fun publishResults(constraint: CharSequence, results: FilterResults) {
+
+ mFilteredData = results.values as Vector<ApplicationInfo>
+ notifyDataSetChanged()
+ }
+
+ }
+
+
+} \ No newline at end of file
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.kt
index f745d2e0..2c113d71 100644
--- a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.kt
@@ -5,47 +5,27 @@
package de.blinkt.openvpn.fragments
-import android.Manifest
-import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
import android.os.Bundle
-import android.text.TextUtils
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import android.widget.AdapterView
-import android.widget.BaseAdapter
-import android.widget.CompoundButton
-import android.widget.Filter
-import android.widget.Filterable
-import android.widget.ImageView
-import android.widget.ListView
import android.widget.Switch
import android.widget.TextView
+import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
-
-import java.util.Collections
-import java.util.Locale
-import java.util.Vector
-
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import de.blinkt.openvpn.R
import de.blinkt.openvpn.VpnProfile
import de.blinkt.openvpn.core.ProfileManager
-import org.jetbrains.anko.runOnUiThread
/**
* Created by arne on 16.11.14.
*/
-class Settings_Allowed_Apps : Fragment(), AdapterView.OnItemClickListener, CompoundButton.OnCheckedChangeListener, View.OnClickListener {
- private lateinit var mListView: ListView
+class Settings_Allowed_Apps : Fragment(), AdapterView.OnItemClickListener, View.OnClickListener {
+ private lateinit var mListView: RecyclerView
private lateinit var mProfile: VpnProfile
- private lateinit var mDefaultAllowTextView: TextView
- private lateinit var mListAdapter: PackageAdapter
+ private lateinit var packageAdapter: PackageAdapter
private lateinit var mSettingsView: View
@@ -58,18 +38,8 @@ class Settings_Allowed_Apps : Fragment(), AdapterView.OnItemClickListener, Compo
}
- override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
- val packageName = buttonView.tag as String
- if (isChecked) {
- mProfile.mAllowedAppsVpn.add(packageName)
- } else {
- mProfile.mAllowedAppsVpn.remove(packageName)
- }
- }
-
override fun onResume() {
super.onResume()
- changeDisallowText(mProfile.mAllowedAppsVpnAreDisallowed)
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -87,21 +57,21 @@ class Settings_Allowed_Apps : Fragment(), AdapterView.OnItemClickListener, Compo
val searchView = menu.findItem(R.id.app_search_widget).actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
- mListView.setFilterText(query)
- mListView.isTextFilterEnabled = true
+ packageAdapter.filter.filter(query)
return true
}
- override fun onQueryTextChange(newText: String): Boolean {
- mListView.setFilterText(newText)
- mListView.isTextFilterEnabled = !TextUtils.isEmpty(newText)
+ override fun onQueryTextChange(query: String): Boolean {
+ packageAdapter.filter.filter(query)
+ //mListView.setFilterText(newText)
+ //mListView.isTextFilterEnabled = !TextUtils.isEmpty(newText)
return true
}
})
searchView.setOnCloseListener {
- mListView.clearTextFilter()
- mListAdapter.filter.filter("")
+ //mListView.clearTextFilter()
+ packageAdapter.filter.filter("")
false
}
@@ -113,213 +83,21 @@ class Settings_Allowed_Apps : Fragment(), AdapterView.OnItemClickListener, Compo
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.allowed_vpn_apps, container, false)
- mSettingsView = inflater.inflate(R.layout.allowed_application_settings, container, false)
- mDefaultAllowTextView = mSettingsView.findViewById<View>(R.id.default_allow_text) as TextView
-
- val vpnOnDefaultSwitch = mSettingsView.findViewById<View>(R.id.default_allow) as Switch
-
- vpnOnDefaultSwitch.setOnCheckedChangeListener { _, isChecked ->
- changeDisallowText(isChecked)
- mProfile.mAllowedAppsVpnAreDisallowed = isChecked
- }
-
- vpnOnDefaultSwitch.isChecked = mProfile.mAllowedAppsVpnAreDisallowed
-
- val vpnAllowBypassSwitch = mSettingsView.findViewById<View>(R.id.allow_bypass) as Switch
+ mListView = v.findViewById<View>(R.id.app_recycler_view) as RecyclerView
- vpnAllowBypassSwitch.setOnCheckedChangeListener { _, isChecked -> mProfile.mAllowAppVpnBypass = isChecked }
+ packageAdapter = PackageAdapter(requireContext(), mProfile)
+ mListView.setHasFixedSize(true)
+ mListView.adapter = packageAdapter
- vpnAllowBypassSwitch.isChecked = mProfile.mAllowAppVpnBypass
-
- mListView = v.findViewById<View>(android.R.id.list) as ListView
-
- mListAdapter = PackageAdapter(requireContext(), mProfile)
- mListView.adapter = mListAdapter
- mListView.onItemClickListener = this
-
- mListView.emptyView = v.findViewById(R.id.loading_container)
-
- Thread(Runnable { mListAdapter.populateList(requireContext()) }).start()
+ Thread(Runnable {
+ packageAdapter.populateList(requireContext())
+ activity?.runOnUiThread({
+ (v.findViewById<View>(R.id.loading_container)).visibility = View.GONE
+ (v.findViewById<View>(R.id.app_recycler_view)).visibility = View.VISIBLE
+ })
+ }).start()
return v
}
- private fun changeDisallowText(selectedAreDisallowed: Boolean) {
- if (selectedAreDisallowed)
- mDefaultAllowTextView.setText(R.string.vpn_disallow_radio)
- else
- mDefaultAllowTextView.setText(R.string.vpn_allow_radio)
- }
-
- internal class AppViewHolder {
- var mInfo: ApplicationInfo? = null
- var rootView: View? = null
- lateinit var appName: TextView
- lateinit var appIcon: ImageView
- //public TextView appSize;
- //public TextView disabled;
- lateinit var checkBox: CompoundButton
-
- companion object {
-
- fun createOrRecycle(inflater: LayoutInflater, oldview: View?, parent: ViewGroup): AppViewHolder {
- var convertView = oldview
- if (convertView == null) {
- convertView = inflater.inflate(R.layout.allowed_application_layout, parent, false)
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- val holder = AppViewHolder()
- holder.rootView = convertView
- holder.appName = convertView.findViewById<View>(R.id.app_name) as TextView
- holder.appIcon = convertView.findViewById<View>(R.id.app_icon) as ImageView
- //holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
- //holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
- holder.checkBox = convertView.findViewById<View>(R.id.app_selected) as CompoundButton
- convertView.tag = holder
-
-
- return holder
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- return convertView.tag as AppViewHolder
- }
- }
- }
-
- }
-
- internal inner class PackageAdapter(c: Context, vp: VpnProfile) : BaseAdapter(), Filterable {
- private val mInflater: LayoutInflater = LayoutInflater.from(c)
- private val mPm: PackageManager = c.packageManager
- private var mPackages: Vector<ApplicationInfo> = Vector()
- private val mFilter = ItemFilter()
- private var mFilteredData: Vector<ApplicationInfo> = mPackages
- private val mProfile = vp
-
-
- fun populateList(c: Context) {
- val installedPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA)
-
- // Remove apps not using Internet
-
- var androidSystemUid = 0
- val apps = Vector<ApplicationInfo>()
-
- try {
- val system = mPm.getApplicationInfo("android", PackageManager.GET_META_DATA)
- androidSystemUid = system.uid
- apps.add(system)
- } catch (e: PackageManager.NameNotFoundException) {
- }
-
-
- for (app in installedPackages) {
-
- if (mPm.checkPermission(Manifest.permission.INTERNET, app.packageName) == PackageManager.PERMISSION_GRANTED && app.uid != androidSystemUid) {
-
- apps.add(app)
- }
- }
-
- Collections.sort(apps, ApplicationInfo.DisplayNameComparator(mPm))
- mPackages = apps
- mFilteredData = apps
- c.runOnUiThread { notifyDataSetChanged() }
- }
-
- override fun getCount(): Int {
- return mFilteredData.size + 1
- }
-
- override fun getItem(position: Int): Any {
- return mFilteredData[position - 1]
- }
-
- override fun getItemId(position: Int): Long {
- if (position == 0)
- return "settings".hashCode().toLong()
- return mFilteredData[position - 1].packageName.hashCode().toLong()
- }
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
- if (position == 0) {
- return mSettingsView
- } else
- return getViewApp(position - 1, convertView, parent)
-
- }
-
- fun getViewApp(position: Int, convertView: View?, parent: ViewGroup): View? {
- val viewHolder = AppViewHolder.createOrRecycle(mInflater, convertView, parent)
- viewHolder.mInfo = mFilteredData[position]
- val mInfo = mFilteredData[position]
-
-
- var appName = mInfo.loadLabel(mPm)
-
- if (TextUtils.isEmpty(appName))
- appName = mInfo.packageName
- viewHolder.appName.text = appName
- viewHolder.appIcon.setImageDrawable(mInfo.loadIcon(mPm))
- viewHolder.checkBox.tag = mInfo.packageName
- viewHolder.checkBox.setOnCheckedChangeListener(this@Settings_Allowed_Apps)
-
-
- viewHolder.checkBox.isChecked = mProfile.mAllowedAppsVpn.contains(mInfo.packageName)
- return viewHolder.rootView
- }
-
- override fun getFilter(): Filter {
- return mFilter
- }
-
- override fun getViewTypeCount(): Int {
- return 2;
- }
-
- override fun getItemViewType(position: Int): Int {
- return if (position == 0) 0 else 1
- }
-
- private inner class ItemFilter : Filter() {
- override fun performFiltering(constraint: CharSequence): FilterResults {
-
- val filterString = constraint.toString().toLowerCase(Locale.getDefault())
-
- val results = FilterResults()
-
-
- val count = mPackages.size
- val nlist = Vector<ApplicationInfo>(count)
-
- for (i in 0 until count) {
- val pInfo = mPackages[i]
- var appName = pInfo.loadLabel(mPm)
-
- if (TextUtils.isEmpty(appName))
- appName = pInfo.packageName
-
- if (appName is String) {
- if (appName.toLowerCase(Locale.getDefault()).contains(filterString))
- nlist.add(pInfo)
- } else {
- if (appName.toString().toLowerCase(Locale.getDefault()).contains(filterString))
- nlist.add(pInfo)
- }
- }
- results.values = nlist
- results.count = nlist.size
-
- return results
- }
-
- override fun publishResults(constraint: CharSequence, results: FilterResults) {
- mFilteredData = results.values as Vector<ApplicationInfo>
- notifyDataSetChanged()
- }
-
- }
- }
}