/* * 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.app.Activity 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 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(R.id.app_name) as TextView holder.appIcon = view.findViewById(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(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(), Filterable, CompoundButton.OnCheckedChangeListener { private val mInflater: LayoutInflater = LayoutInflater.from(c) private val mPm: PackageManager = c.packageManager private var mPackages: Vector = Vector() private val mFilter = ItemFilter() private var mFilteredData: Vector = mPackages private val mProfile = vp init { setHasStableIds(true) } fun populateList(a: Activity) { val installedPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA) // Remove apps not using Internet var androidSystemUid = 0 val apps = Vector() 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 a.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(R.id.default_allow_text) as TextView val vpnOnDefaultSwitch = settingsView.findViewById(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(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(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 notifyDataSetChanged() } } }