diff --git a/.idea/misc.xml b/.idea/misc.xml index c9f8a446..85bfba47 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -93,7 +93,7 @@ - + diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt index 39ed4123..2fec94f6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt @@ -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 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(WifiP2pManager.EXTRA_P2P_DEVICE_LIST).deviceList + binder.data?.onClientsChanged() + } + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { + val info = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO) + val net = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) + val group = intent.getParcelableExtra(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(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,70 +151,48 @@ 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 { - 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") - } - }) - } + private fun doStart() = p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener { + override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (reason: $reason)") + 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", - "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", - "iptables -X vpnhotspot_fwd", - "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() + 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", + "iptables -X vpnhotspot_fwd", + "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")) { + 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() } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt index 312d4385..8d396909 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt @@ -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() { + private var owner: WifiP2pDevice? = null + private lateinit var clients: MutableCollection + + 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() } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt index 586f53be..a96ccd46 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt @@ -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 } diff --git a/mobile/src/main/res/layout/client.xml b/mobile/src/main/res/layout/client.xml new file mode 100644 index 00000000..fe85c2f8 --- /dev/null +++ b/mobile/src/main/res/layout/client.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/res/layout/main_activity.xml b/mobile/src/main/res/layout/main_activity.xml index acf98b7c..24981173 100644 --- a/mobile/src/main/res/layout/main_activity.xml +++ b/mobile/src/main/res/layout/main_activity.xml @@ -22,7 +22,6 @@ android:id="@+id/toolbar"> @@ -93,11 +92,15 @@ android:backgroundTint="?android:attr/textColorSecondary"/> + android:paddingTop="8dp" + android:clipToPadding="false" + android:scrollbars="vertical" + tools:listitem="@layout/client"/>