Implement connected devices (buggy debug version)

This commit is contained in:
Mygod
2018-01-03 17:57:25 +08:00
parent b073109d4f
commit e486c8fcf6
6 changed files with 192 additions and 78 deletions

2
.idea/misc.xml generated
View File

@@ -93,7 +93,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -5,8 +5,8 @@ import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager
import android.net.NetworkInfo
import android.net.wifi.p2p.*
import android.os.Binder
import android.os.Looper
import android.support.v4.app.NotificationCompat
@@ -45,18 +45,61 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
private lateinit var p2pManager: WifiP2pManager
private lateinit var channel: WifiP2pManager.Channel
private lateinit var group: WifiP2pGroup
private var receiver: BroadcastReceiver? = null
lateinit var group: WifiP2pGroup
lateinit var clients: MutableCollection<WifiP2pDevice>
private val binder = HotspotBinder()
private var receiverRegistered = false
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION ->
if (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0) ==
WifiP2pManager.WIFI_P2P_STATE_DISABLED) clean() // group may be enabled by other apps
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION ->{
clients = intent.getParcelableExtra<WifiP2pDeviceList>(WifiP2pManager.EXTRA_P2P_DEVICE_LIST).deviceList
binder.data?.onClientsChanged()
}
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
val net = intent.getParcelableExtra<NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
val group = intent.getParcelableExtra<WifiP2pGroup>(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
val downstream = group.`interface`
if (net.isConnected && downstream != null && this@HotspotService.downstream == null) {
this@HotspotService.downstream = downstream
if (noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward",
"ip route add default dev $upstream scope link table 62",
"ip route add $route dev $downstream scope link table 62",
"ip route add broadcast 255.255.255.255 dev $downstream scope link table 62",
"ip rule add from $route lookup 62",
"iptables -N vpnhotspot_fwd",
"iptables -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT",
"iptables -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT",
"iptables -I FORWARD -j vpnhotspot_fwd",
"iptables -t nat -A PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"iptables -t nat -A PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns")) {
doStart(group)
} else startFailure("Something went wrong, please check logcat.")
}
group.`interface`
binder.data?.onGroupChanged()
Log.d(TAG, "${intent.action}: $info, $net, $group")
}
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
Log.d(TAG, "${intent.action}: $info")
}
}
}
}
// TODO: do something
// TODO: do something to these hardcoded strings
private var downstream: String? = null
private val upstream = "tun0"
private val downstream = "p2p0"
private val route = "192.168.49.0/24"
private val dns = "8.8.8.8:53"
private var status = Status.IDLE
set(value) {
var status = Status.IDLE
private set(value) {
field = value
binder.data?.onStatusChanged()
}
@@ -76,20 +119,13 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (status != Status.IDLE) return START_NOT_STICKY
status = Status.STARTING
initReceiver()
if (!noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward",
"ip route add default dev $upstream scope link table 62",
"ip route add $route dev $downstream scope link table 62",
"ip route add broadcast 255.255.255.255 dev $downstream scope link table 62",
"ip rule add from $route lookup 62",
"iptables -N vpnhotspot_fwd",
"iptables -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT",
"iptables -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT",
"iptables -I FORWARD -j vpnhotspot_fwd",
"iptables -t nat -A PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"iptables -t nat -A PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns")) {
startFailure("Something went wrong, please check logcat.")
return START_NOT_STICKY
if (!receiverRegistered) {
registerReceiver(receiver, createIntentFilter(
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
receiverRegistered = true
}
p2pManager.requestGroupInfo(channel, {
when {
@@ -115,51 +151,32 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
startForeground(0, NotificationCompat.Builder(this@HotspotService, CHANNEL).build())
clean()
}
private fun doStart() {
p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener, WifiP2pManager.GroupInfoListener {
private fun doStart() = p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener {
override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (reason: $reason)")
private var tries = 0
override fun onSuccess() = p2pManager.requestGroupInfo(channel, this)
override fun onGroupInfoAvailable(group: WifiP2pGroup?) {
if (group != null && group.isGroupOwner) doStart(group) else if (tries < 10) {
Thread.sleep(30L shl tries++)
onSuccess()
} else startFailure("Unexpected group: $group")
}
override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire
})
}
private fun doStart(group: WifiP2pGroup) {
status = Status.ACTIVE
this.group = group
clients = group.clientList
status = Status.ACTIVE
startForeground(1, NotificationCompat.Builder(this@HotspotService, CHANNEL)
.setWhen(0)
.setColor(ContextCompat.getColor(this@HotspotService, R.color.colorPrimary))
.setContentTitle(group.networkName)
.setSubText(group.passphrase)
.setContentText(group.passphrase)
.setSmallIcon(R.drawable.ic_device_wifi_tethering)
.setContentIntent(PendingIntent.getActivity(this, 0,
Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
.build())
}
private fun initReceiver() {
return
receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
// TODO
}
}
}
registerReceiver(receiver, createIntentFilter(
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
}
private fun clean() {
if (!noisySu("iptables -t nat -D PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
if (receiverRegistered) {
unregisterReceiver(receiver)
receiverRegistered = false
}
if (downstream != null)
if (noisySu("iptables -t nat -D PREROUTING -i $downstream -p tcp --dport 53 -j DNAT --to-destination $dns",
"iptables -t nat -D PREROUTING -i $downstream -p udp --dport 53 -j DNAT --to-destination $dns",
"iptables -D FORWARD -j vpnhotspot_fwd",
"iptables -F vpnhotspot_fwd",
@@ -167,18 +184,15 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
"ip rule del from $route lookup 62",
"ip route del broadcast 255.255.255.255 dev $downstream scope link table 62",
"ip route del $route dev $downstream scope link table 62",
"ip route del default dev $upstream scope link table 62"))
Toast.makeText(this, "Something went wrong, please check logcat.", Toast.LENGTH_SHORT).show()
"ip route del default dev $upstream scope link table 62")) {
downstream = null
} else Toast.makeText(this, "Something went wrong, please check logcat.", Toast.LENGTH_SHORT).show()
status = Status.IDLE
stopForeground(true)
}
override fun onDestroy() {
if (status != Status.IDLE) binder.shutdown()
if (receiver != null) {
unregisterReceiver(receiver)
receiver = null
}
super.onDestroy()
}
}

View File

@@ -7,10 +7,17 @@ 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.content.ContextCompat
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.view.LayoutInflater
import android.view.ViewGroup
import be.mygod.vpnhotspot.databinding.ClientBinding
import be.mygod.vpnhotspot.databinding.MainActivityBinding
class MainActivity : AppCompatActivity(), ServiceConnection {
@@ -37,23 +44,66 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
}
}
val running get() = binder?.status == HotspotService.Status.ACTIVE
val ssid: String @Bindable get() = if (running) binder!!.service.group.networkName else ""
val password: String @Bindable get() = if (running) binder!!.service.group.passphrase else ""
val clients @Bindable get() = if (running) binder!!.service.clients else null
fun onStatusChanged() {
notifyPropertyChanged(BR.switchEnabled)
notifyPropertyChanged(BR.serviceStarted)
onGroupChanged()
onClientsChanged()
}
fun onBinderChanged() {
onStatusChanged()
fun onGroupChanged() {
notifyPropertyChanged(BR.ssid)
notifyPropertyChanged(BR.password)
}
fun onClientsChanged() = adapter.fetchClients()
}
class ClientViewHolder(val binding: ClientBinding) : RecyclerView.ViewHolder(binding.root)
inner class ClientAdapter : RecyclerView.Adapter<ClientViewHolder>() {
private var owner: WifiP2pDevice? = null
private lateinit var clients: MutableCollection<WifiP2pDevice>
fun fetchClients() {
val binder = binder!!
if (data.running) {
owner = binder.service.group.owner
clients = binder.service.clients
} else owner = null
notifyDataSetChanged() // recreate everything
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ClientViewHolder(ClientBinding.inflate(LayoutInflater.from(parent.context)))
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
holder.binding.device = when (position) {
0 -> owner
else -> clients.elementAt(position - 1)
}
holder.binding.executePendingBindings()
}
override fun getItemCount() = if (owner == null) 0 else 1 + clients.size
}
private lateinit var binding: MainActivityBinding
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.main_activity)
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
}
override fun onStart() {
@@ -70,11 +120,12 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
val binder = service as HotspotService.HotspotBinder
binder.data = data
this.binder = binder
data.onBinderChanged()
data.onStatusChanged()
}
override fun onServiceDisconnected(name: ComponentName?) {
binder?.data = null
binder = null
data.onStatusChanged()
}
}

View File

@@ -21,8 +21,8 @@ echo $NOISYSU_SUFFIX""")
var out = process.inputStream.bufferedReader().use { it.readLine() }
val result = out != NOISYSU_SUFFIX
out = out.removeSuffix(NOISYSU_SUFFIX)
if (!out.isNullOrBlank()) Log.i("noisySu", out)
if (!out.isNullOrBlank()) Log.i(NOISYSU_TAG, out)
val err = process.errorStream.bufferedReader().use { it.readLine() }
if (!err.isNullOrBlank()) Log.e("noisySu", err)
if (!err.isNullOrBlank()) Log.e(NOISYSU_TAG, err)
return result
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="device"
type="android.net.wifi.p2p.WifiP2pDevice"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.deviceName}"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
tools:text="Device name"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.deviceAddress}"
tools:text="xx:xx:xx:xx:xx:xx"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.primaryDeviceType}"
tools:text="Primary type"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{device.secondaryDeviceType}"
tools:text="Secondary type"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Integer.toString(device.status)}"
tools:text="Status"/>
</LinearLayout>
</layout>

View File

@@ -22,7 +22,6 @@
android:id="@+id/toolbar">
<Switch
android:id="@+id/switchService"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
@@ -54,11 +53,11 @@
android:layout_row="0"/>
<TextView
android:id="@+id/textSsid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_row="0"
android:text="@{data.ssid}"
tools:text="DIRECT-rAnd0m"/>
<TextView
@@ -70,11 +69,11 @@
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
<TextView
android:id="@+id/textPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_row="1"
android:text="@{data.password}"
tools:text="p4ssW0rd"/>
</GridLayout>
@@ -93,11 +92,15 @@
android:backgroundTint="?android:attr/textColorSecondary"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/clients"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp"/>
android:paddingTop="8dp"
android:clipToPadding="false"
android:scrollbars="vertical"
tools:listitem="@layout/client"/>
</LinearLayout>
</layout>