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,146 @@
package be.mygod.vpnhotspot
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.databinding.BaseObservable
import android.databinding.Bindable
import android.databinding.DataBindingUtil
import android.net.wifi.p2p.WifiP2pDevice
import android.os.Bundle
import android.os.IBinder
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import android.support.v4.content.LocalBroadcastManager
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.databinding.FragmentRepeaterBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
class RepeaterFragment : Fragment(), ServiceConnection {
inner class Data : BaseObservable() {
val switchEnabled: Boolean
@Bindable get() = when (binder?.service?.status) {
RepeaterService.Status.IDLE, RepeaterService.Status.ACTIVE -> true
else -> false
}
var serviceStarted: Boolean
@Bindable get() = when (binder?.service?.status) {
RepeaterService.Status.STARTING, RepeaterService.Status.ACTIVE -> true
else -> false
}
set(value) {
val binder = binder
when (binder?.service?.status) {
RepeaterService.Status.IDLE ->
if (value) {
val context = context!!
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
}
RepeaterService.Status.ACTIVE -> if (!value) binder.shutdown()
}
}
val ssid @Bindable get() = binder?.service?.ssid ?: "Service inactive"
val password @Bindable get() = binder?.service?.password ?: ""
fun onStatusChanged() {
notifyPropertyChanged(BR.switchEnabled)
notifyPropertyChanged(BR.serviceStarted)
onGroupChanged()
}
fun onGroupChanged() {
notifyPropertyChanged(BR.ssid)
notifyPropertyChanged(BR.password)
adapter.fetchClients()
}
val statusListener = broadcastReceiver { _, _ -> onStatusChanged() }
}
class ClientViewHolder(val binding: ListitemClientBinding) : RecyclerView.ViewHolder(binding.root)
inner class ClientAdapter : RecyclerView.Adapter<ClientViewHolder>() {
private var owner: WifiP2pDevice? = null
private lateinit var clients: MutableCollection<WifiP2pDevice>
private lateinit var arpCache: Map<String, String>
fun fetchClients() {
val binder = binder
if (binder?.service?.status == RepeaterService.Status.ACTIVE) {
owner = binder.service.group.owner
clients = binder.service.group.clientList
arpCache = NetUtils.arp(binder.service.routing?.downstream)
} else owner = null
notifyDataSetChanged() // recreate everything
binding.swipeRefresher.isRefreshing = false
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ClientViewHolder(ListitemClientBinding.inflate(LayoutInflater.from(parent.context)))
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
val device = when (position) {
0 -> owner
else -> clients.elementAt(position - 1)
}
holder.binding.device = device
holder.binding.ipAddress = when (position) {
0 -> binder?.service?.routing?.hostAddress?.hostAddress
else -> arpCache[device?.deviceAddress]
}
holder.binding.executePendingBindings()
}
override fun getItemCount() = if (owner == null) 0 else 1 + clients.size
}
private lateinit var binding: FragmentRepeaterBinding
private val data = Data()
private val adapter = ClientAdapter()
private var binder: RepeaterService.HotspotBinder? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_repeater, container, false)
binding.data = data
binding.clients.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
binding.clients.itemAnimator = animator
binding.clients.adapter = adapter
binding.swipeRefresher.setOnRefreshListener { adapter.fetchClients() }
return binding.root
}
override fun onStart() {
super.onStart()
val context = context!!
context.bindService(Intent(context, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
onServiceDisconnected(null)
context!!.unbindService(this)
super.onStop()
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as RepeaterService.HotspotBinder
binder.data = data
this.binder = binder
data.onStatusChanged()
LocalBroadcastManager.getInstance(context!!)
.registerReceiver(data.statusListener, intentFilter(RepeaterService.STATUS_CHANGED))
}
override fun onServiceDisconnected(name: ComponentName?) {
binder?.data = null
binder = null
LocalBroadcastManager.getInstance(context!!).unregisterReceiver(data.statusListener)
data.onStatusChanged()
}
}