Files
vpnhotspotmod/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt
2018-01-21 12:50:21 -08:00

122 lines
4.8 KiB
Kotlin

package be.mygod.vpnhotspot
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.support.v4.content.LocalBroadcastManager
import android.widget.Toast
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.*
class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Callback {
companion object {
const val EXTRA_ADD_INTERFACE = "interface.add"
const val EXTRA_REMOVE_INTERFACE = "interface.remove"
}
inner class TetheringBinder : Binder() {
val active get() = routings.keys
var fragment: TetheringFragment? = null
}
private val binder = TetheringBinder()
private val routings = HashMap<String, Routing?>()
private var neighbours = emptyList<IpNeighbour>()
private var upstream: String? = null
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
when (intent.action) {
ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED -> {
val remove = routings.keys - ConnectivityManagerHelper.getTetheredIfaces(intent.extras)
if (remove.isEmpty()) return@broadcastReceiver
val failed = remove.any { routings.remove(it)?.stop() == false }
if (failed) Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show()
}
App.ACTION_CLEAN_ROUTINGS -> for (iface in routings.keys) routings[iface] = null
}
updateRoutings()
}
private fun updateRoutings() {
if (routings.isEmpty()) {
unregisterReceiver()
ServiceNotification.stopForeground(this)
stopSelf()
} else {
val upstream = upstream
if (upstream != null) {
var failed = false
for ((downstream, value) in routings) if (value == null) {
val routing = Routing(upstream, downstream).rule().forward().dnsRedirect(app.dns)
if (routing.start()) routings[downstream] = routing else {
failed = true
routing.stop()
routings.remove(downstream)
}
}
if (failed) Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show()
} else if (!receiverRegistered) {
registerReceiver(receiver, intentFilter(ConnectivityManagerHelper.ACTION_TETHER_STATE_CHANGED))
LocalBroadcastManager.getInstance(this)
.registerReceiver(receiver, intentFilter(App.ACTION_CLEAN_ROUTINGS))
IpNeighbourMonitor.registerCallback(this)
VpnMonitor.registerCallback(this)
receiverRegistered = true
}
postIpNeighbourAvailable()
}
app.handler.post { binder.fragment?.adapter?.notifyDataSetChanged() }
}
override fun onBind(intent: Intent?) = binder
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val iface = intent.getStringExtra(EXTRA_ADD_INTERFACE)
if (iface != null) routings[iface] = null
if (routings.remove(intent.getStringExtra(EXTRA_REMOVE_INTERFACE))?.stop() == false)
Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show()
updateRoutings()
return START_NOT_STICKY
}
override fun onAvailable(ifname: String) {
check(upstream == null || upstream == ifname)
upstream = ifname
updateRoutings()
}
override fun onLost(ifname: String) {
check(upstream == null || upstream == ifname)
upstream = null
var failed = false
for ((iface, routing) in routings) {
if (routing?.stop() == false) failed = true
routings[iface] = null
}
if (failed) Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show()
}
override fun onIpNeighbourAvailable(neighbours: Map<String, IpNeighbour>) {
this.neighbours = neighbours.values.toList()
}
override fun postIpNeighbourAvailable() {
val sizeLookup = neighbours.groupBy { it.dev }.mapValues { (_, neighbours) -> neighbours.size }
ServiceNotification.startForeground(this, routings.keys.associate { Pair(it, sizeLookup[it] ?: 0) })
}
override fun onDestroy() {
unregisterReceiver()
super.onDestroy()
}
private fun unregisterReceiver() {
if (receiverRegistered) {
unregisterReceiver(receiver)
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
VpnMonitor.unregisterCallback(this)
upstream = null
receiverRegistered = false
}
}
}