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

@@ -1,160 +1,42 @@
package be.mygod.vpnhotspot
import android.content.*
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.content.ContextCompat
import android.support.v4.content.LocalBroadcastManager
import android.support.design.widget.BottomNavigationView
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemClickListener {
inner class Data : BaseObservable() {
val switchEnabled: Boolean
@Bindable get() = when (binder?.service?.status) {
HotspotService.Status.IDLE, HotspotService.Status.ACTIVE_P2P, HotspotService.Status.ACTIVE_AP -> true
else -> false
}
var serviceStarted: Boolean
@Bindable get() = when (binder?.service?.status) {
HotspotService.Status.STARTING, HotspotService.Status.ACTIVE_P2P, HotspotService.Status.ACTIVE_AP ->
true
else -> false
}
set(value) {
val binder = binder
when (binder?.service?.status) {
HotspotService.Status.IDLE ->
if (value) ContextCompat.startForegroundService(this@MainActivity,
Intent(this@MainActivity, HotspotService::class.java))
HotspotService.Status.ACTIVE_P2P, HotspotService.Status.ACTIVE_AP -> 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 == HotspotService.Status.ACTIVE_P2P) {
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
}
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding
private val data = Data()
private val adapter = ClientAdapter()
private var binder: HotspotService.HotspotBinder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.data = data
binding.clients.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
val animator = DefaultItemAnimator()
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
binding.clients.itemAnimator = animator
binding.clients.adapter = adapter
binding.toolbar.inflateMenu(R.menu.main)
binding.toolbar.setOnMenuItemClickListener(this)
binding.swipeRefresher.setOnRefreshListener { adapter.fetchClients() }
binding.navigation.setOnNavigationItemSelectedListener(this)
onNavigationItemSelected(binding.navigation.menu.getItem(0))
}
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.reapply -> {
val binder = binder
when (binder?.service?.status) {
HotspotService.Status.IDLE -> Routing.clean()
HotspotService.Status.ACTIVE_P2P, HotspotService.Status.ACTIVE_AP -> binder.reapplyRouting()
}
override fun onNavigationItemSelected(item: MenuItem) = when (item.itemId) {
R.id.navigation_repeater -> {
item.isChecked = true
displayFragment(RepeaterFragment())
true
}
R.id.settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
R.id.navigation_tethering -> {
item.isChecked = true
displayFragment(TetheringFragment())
true
}
R.id.navigation_settings -> {
item.isChecked = true
displayFragment(SettingsFragment())
true
}
else -> false
}
override fun onStart() {
super.onStart()
bindService(Intent(this, HotspotService::class.java), this, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
onServiceDisconnected(null)
unbindService(this)
super.onStop()
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as HotspotService.HotspotBinder
binder.data = data
this.binder = binder
data.onStatusChanged()
LocalBroadcastManager.getInstance(this)
.registerReceiver(data.statusListener, intentFilter(HotspotService.STATUS_CHANGED))
}
override fun onServiceDisconnected(name: ComponentName?) {
binder?.data = null
binder = null
LocalBroadcastManager.getInstance(this).unregisterReceiver(data.statusListener)
data.onStatusChanged()
}
private fun displayFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().replace(R.id.fragmentHolder, fragment).commit()
}