Support VPN over any native tethering

First big refactoring of this app.
This commit is contained in:
Mygod
2018-01-13 00:41:55 +08:00
parent f341bf409e
commit eb165db86c
31 changed files with 1068 additions and 874 deletions

View File

@@ -0,0 +1,141 @@
package be.mygod.vpnhotspot
import android.content.Intent
import android.content.res.Resources
import android.databinding.BaseObservable
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.content.LocalBroadcastManager
import android.support.v7.util.SortedList
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.NetUtils.tetheredIfaces
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
class TetheringFragment : Fragment() {
companion object {
/**
* Source: https://android.googlesource.com/platform/frameworks/base/+/61fa313/core/res/res/values/config.xml#328
*/
private val usbRegexes = app.resources.getStringArray(Resources.getSystem()
.getIdentifier("config_tether_usb_regexs", "array", "android"))
.map { it.toPattern() }
private val wifiRegexes = app.resources.getStringArray(Resources.getSystem()
.getIdentifier("config_tether_wifi_regexs", "array", "android"))
.map { it.toPattern() }
private val wimaxRegexes = app.resources.getStringArray(Resources.getSystem()
.getIdentifier("config_tether_wimax_regexs", "array", "android"))
.map { it.toPattern() }
private val bluetoothRegexes = app.resources.getStringArray(Resources.getSystem()
.getIdentifier("config_tether_bluetooth_regexs", "array", "android"))
.map { it.toPattern() }
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() {
override fun onInserted(position: Int, count: Int) { }
override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem
override fun onMoved(fromPosition: Int, toPosition: Int) { }
override fun onChanged(position: Int, count: Int) { }
override fun onRemoved(position: Int, count: Int) { }
override fun areItemsTheSame(item1: T?, item2: T?): Boolean = item1 == item2
override fun compare(o1: T?, o2: T?): Int =
if (o1 == null) if (o2 == null) 0 else 1 else if (o2 == null) -1 else compareNonNull(o1, o2)
abstract fun compareNonNull(o1: T, o2: T): Int
}
private open class DefaultSorter<T : Comparable<T>> : BaseSorter<T>() {
override fun compareNonNull(o1: T, o2: T): Int = o1.compareTo(o2)
}
private object StringSorter : DefaultSorter<String>()
class Data(val iface: String) : BaseObservable() {
val icon: Int get() = when {
usbRegexes.any { it.matcher(iface).matches() } -> R.drawable.ic_device_usb
wifiRegexes.any { it.matcher(iface).matches() } -> R.drawable.ic_device_network_wifi
wimaxRegexes.any { it.matcher(iface).matches() } -> R.drawable.ic_device_network_wifi
bluetoothRegexes.any { it.matcher(iface).matches() } -> R.drawable.ic_device_bluetooth
else -> R.drawable.ic_device_wifi_tethering
}
var active = TetheringService.active?.contains(iface) == true
}
class InterfaceViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val context = itemView.context
val data = binding.data!!
context.startService(Intent(context, TetheringService::class.java).putExtra(if (data.active)
TetheringService.EXTRA_REMOVE_INTERFACE else TetheringService.EXTRA_ADD_INTERFACE, data.iface))
data.active = !data.active
}
}
inner class InterfaceAdapter : RecyclerView.Adapter<InterfaceViewHolder>() {
private val tethered = SortedList(String::class.java, StringSorter)
fun update(data: Set<String> = VpnListener.connectivityManager.tetheredIfaces.toSet()) {
tethered.clear()
tethered.addAll(data)
notifyDataSetChanged()
}
override fun getItemCount() = tethered.size()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = InterfaceViewHolder(
ListitemInterfaceBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: InterfaceViewHolder, position: Int) {
holder.binding.data = Data(tethered[position])
}
}
private val adapter = InterfaceAdapter()
private val receiver = broadcastReceiver { _, intent ->
when (intent.action) {
TetheringService.ACTION_ACTIVE_INTERFACES_CHANGED -> adapter.notifyDataSetChanged()
NetUtils.ACTION_TETHER_STATE_CHANGED ->
adapter.update(intent.extras.getStringArrayList(NetUtils.EXTRA_ACTIVE_TETHER).toSet())
}
}
private var receiverRegistered = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<FragmentTetheringBinding>(inflater, R.layout.fragment_tethering,
container, false)
binding.interfaces.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
binding.interfaces.itemAnimator = animator
binding.interfaces.adapter = adapter
return binding.root
}
override fun onStart() {
super.onStart()
if (!receiverRegistered) {
adapter.update()
val context = context!!
context.registerReceiver(receiver, intentFilter(NetUtils.ACTION_TETHER_STATE_CHANGED))
LocalBroadcastManager.getInstance(context)
.registerReceiver(receiver, intentFilter(TetheringService.ACTION_ACTIVE_INTERFACES_CHANGED))
receiverRegistered = true
}
}
override fun onStop() {
if (receiverRegistered) {
val context = context!!
context.unregisterReceiver(receiver)
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
receiverRegistered = false
}
super.onStop()
}
}