Add settings
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -13,7 +12,6 @@ android {
|
|||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.0.1"
|
versionName "0.0.1"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary true
|
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
@@ -29,6 +27,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||||
|
implementation 'com.android.support:design:27.0.2'
|
||||||
kapt "com.android.databinding:compiler:$androidPluginVersion"
|
kapt "com.android.databinding:compiler:$androidPluginVersion"
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "com.android.support:customtabs:$supportLibraryVersion"
|
implementation "com.android.support:customtabs:$supportLibraryVersion"
|
||||||
|
|||||||
@@ -21,9 +21,12 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service android:name=".HotspotService">
|
||||||
android:name=".HotspotService">
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:label="@string/title_activity_settings"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -4,12 +4,20 @@ import android.app.Application
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
|
companion object {
|
||||||
|
lateinit var app: App
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
app = this
|
||||||
if (Build.VERSION.SDK_INT >= 26) getSystemService(NotificationManager::class.java)
|
if (Build.VERSION.SDK_INT >= 26) getSystemService(NotificationManager::class.java)
|
||||||
.createNotificationChannel(NotificationChannel(HotspotService.CHANNEL,
|
.createNotificationChannel(NotificationChannel(HotspotService.CHANNEL,
|
||||||
"Hotspot Service", NotificationManager.IMPORTANCE_LOW))
|
"Hotspot Service", NotificationManager.IMPORTANCE_LOW))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pref by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,26 @@ package be.mygod.vpnhotspot
|
|||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.NetworkInfo
|
import android.net.NetworkInfo
|
||||||
import android.net.wifi.p2p.*
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
|
import android.net.wifi.p2p.WifiP2pInfo
|
||||||
|
import android.net.wifi.p2p.WifiP2pManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.content.LocalBroadcastManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "hotspot"
|
const val CHANNEL = "hotspot"
|
||||||
|
const val STATUS_CHANGED = "be.mygod.vpnhotspot.HotspotService.STATUS_CHANGED"
|
||||||
private const val TAG = "HotspotService"
|
private const val TAG = "HotspotService"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +31,6 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
|
|
||||||
inner class HotspotBinder : Binder() {
|
inner class HotspotBinder : Binder() {
|
||||||
val service get() = this@HotspotService
|
val service get() = this@HotspotService
|
||||||
val status get() = this@HotspotService.status
|
|
||||||
var data: MainActivity.Data? = null
|
var data: MainActivity.Data? = null
|
||||||
|
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
@@ -35,9 +38,9 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
override fun onSuccess() = clean()
|
override fun onSuccess() = clean()
|
||||||
override fun onFailure(reason: Int) {
|
override fun onFailure(reason: Int) {
|
||||||
if (reason == WifiP2pManager.BUSY) clean() else { // assuming it's already gone
|
if (reason == WifiP2pManager.BUSY) clean() else { // assuming it's already gone
|
||||||
Toast.makeText(this@HotspotService, "Failed to remove P2P group (reason: $reason)",
|
Toast.makeText(this@HotspotService, "Failed to remove P2P group (${formatReason(reason)})",
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
binder.data?.onStatusChanged()
|
LocalBroadcastManager.getInstance(this@HotspotService).sendBroadcast(Intent(STATUS_CHANGED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -52,43 +55,41 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
private set
|
private set
|
||||||
private val binder = HotspotBinder()
|
private val binder = HotspotBinder()
|
||||||
private var receiverRegistered = false
|
private var receiverRegistered = false
|
||||||
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
|
private val receiver = broadcastReceiver { _, intent ->
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
when (intent.action) {
|
||||||
when (intent.action) {
|
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION ->
|
||||||
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION ->
|
if (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0) ==
|
||||||
if (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0) ==
|
WifiP2pManager.WIFI_P2P_STATE_DISABLED) clean() // group may be enabled by other apps
|
||||||
WifiP2pManager.WIFI_P2P_STATE_DISABLED) clean() // group may be enabled by other apps
|
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
|
||||||
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
|
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
|
||||||
val info = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
|
val net = intent.getParcelableExtra<NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
|
||||||
val net = intent.getParcelableExtra<NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
|
val group = intent.getParcelableExtra<WifiP2pGroup>(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
|
||||||
val group = intent.getParcelableExtra<WifiP2pGroup>(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
|
hostAddress = info.groupOwnerAddress
|
||||||
hostAddress = info.groupOwnerAddress
|
val downstream = group.`interface`
|
||||||
val downstream = group.`interface`
|
if (net.isConnected && downstream != null && this@HotspotService.downstream == null) {
|
||||||
if (net.isConnected && downstream != null && this@HotspotService.downstream == null) {
|
this@HotspotService.downstream = downstream
|
||||||
this@HotspotService.downstream = downstream
|
if (noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward",
|
||||||
if (noisySu("echo 1 >/proc/sys/net/ipv4/ip_forward",
|
"ip route add default dev $upstream scope link table 62",
|
||||||
"ip route add default dev $upstream scope link table 62",
|
"ip route add $route dev $downstream 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 route add broadcast 255.255.255.255 dev $downstream scope link table 62",
|
"ip rule add from $route lookup 62",
|
||||||
"ip rule add from $route lookup 62",
|
"iptables -N vpnhotspot_fwd",
|
||||||
"iptables -N vpnhotspot_fwd",
|
"iptables -A vpnhotspot_fwd -i $upstream -o $downstream -m state --state ESTABLISHED,RELATED -j ACCEPT",
|
||||||
"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 -A vpnhotspot_fwd -i $downstream -o $upstream -j ACCEPT",
|
"iptables -I FORWARD -j vpnhotspot_fwd",
|
||||||
"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 tcp --dport 53 -j DNAT --to-destination $dns",
|
"iptables -t nat -A PREROUTING -i $downstream -p udp --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)
|
||||||
doStart(group)
|
} else startFailure("Something went wrong, please check logcat.")
|
||||||
} else startFailure("Something went wrong, please check logcat.")
|
|
||||||
}
|
|
||||||
this@HotspotService.group = group
|
|
||||||
binder.data?.onGroupChanged()
|
|
||||||
showNotification(group)
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
this@HotspotService.group = group
|
||||||
|
binder.data?.onGroupChanged()
|
||||||
|
showNotification(group)
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,16 +97,25 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
// TODO: do something to these hardcoded strings
|
// TODO: do something to these hardcoded strings
|
||||||
var downstream: String? = null
|
var downstream: String? = null
|
||||||
private set
|
private set
|
||||||
private val upstream = "tun0"
|
private val upstream get() = app.pref.getString("service.upstream", "tun0")
|
||||||
private val route = "192.168.49.0/24"
|
private val route get() = app.pref.getString("service.route", "192.168.49.0/24")
|
||||||
private val dns = "8.8.8.8:53"
|
private val dns get() = app.pref.getString("service.dns", "8.8.8.8:53")
|
||||||
|
|
||||||
var status = Status.IDLE
|
var status = Status.IDLE
|
||||||
private set(value) {
|
private set(value) {
|
||||||
|
if (field == value) return
|
||||||
field = value
|
field = value
|
||||||
binder.data?.onStatusChanged()
|
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(STATUS_CHANGED))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatReason(reason: Int) = when (reason) {
|
||||||
|
WifiP2pManager.ERROR -> "ERROR"
|
||||||
|
WifiP2pManager.P2P_UNSUPPORTED -> "P2P_UNSUPPORTED"
|
||||||
|
WifiP2pManager.BUSY -> "BUSY"
|
||||||
|
WifiP2pManager.NO_SERVICE_REQUESTS -> "NO_SERVICE_REQUESTS"
|
||||||
|
else -> "unknown reason: $reason"
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent) = binder
|
override fun onBind(intent: Intent) = binder
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -122,7 +132,7 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
if (status != Status.IDLE) return START_NOT_STICKY
|
if (status != Status.IDLE) return START_NOT_STICKY
|
||||||
status = Status.STARTING
|
status = Status.STARTING
|
||||||
if (!receiverRegistered) {
|
if (!receiverRegistered) {
|
||||||
registerReceiver(receiver, createIntentFilter(
|
registerReceiver(receiver, intentFilter(
|
||||||
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
|
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
|
||||||
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
|
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
|
||||||
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
|
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION))
|
||||||
@@ -137,8 +147,9 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
||||||
override fun onSuccess() = doStart()
|
override fun onSuccess() = doStart()
|
||||||
override fun onFailure(reason: Int) {
|
override fun onFailure(reason: Int) {
|
||||||
Toast.makeText(this@HotspotService, "Failed to remove old P2P group (reason: $reason)",
|
Toast.makeText(this@HotspotService,
|
||||||
Toast.LENGTH_SHORT).show()
|
"Failed to remove old P2P group (${formatReason(reason)})", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -153,7 +164,7 @@ class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
|||||||
clean()
|
clean()
|
||||||
}
|
}
|
||||||
private fun doStart() = p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener {
|
private fun doStart() = p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener {
|
||||||
override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (reason: $reason)")
|
override fun onFailure(reason: Int) = startFailure("Failed to create P2P group (${formatReason(reason)})")
|
||||||
override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire
|
override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire
|
||||||
})
|
})
|
||||||
private fun doStart(group: WifiP2pGroup) {
|
private fun doStart(group: WifiP2pGroup) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.*
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.databinding.BaseObservable
|
import android.databinding.BaseObservable
|
||||||
import android.databinding.Bindable
|
import android.databinding.Bindable
|
||||||
import android.databinding.DataBindingUtil
|
import android.databinding.DataBindingUtil
|
||||||
@@ -11,32 +8,35 @@ import android.net.wifi.p2p.WifiP2pDevice
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.content.LocalBroadcastManager
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.support.v7.widget.DefaultItemAnimator
|
import android.support.v7.widget.DefaultItemAnimator
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.support.v7.widget.Toolbar
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import be.mygod.vpnhotspot.databinding.ClientBinding
|
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
|
||||||
import be.mygod.vpnhotspot.databinding.MainActivityBinding
|
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ServiceConnection {
|
class MainActivity : AppCompatActivity(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
||||||
inner class Data : BaseObservable() {
|
inner class Data : BaseObservable() {
|
||||||
val switchEnabled: Boolean
|
val switchEnabled: Boolean
|
||||||
@Bindable get() = when (binder?.status) {
|
@Bindable get() = when (binder?.service?.status) {
|
||||||
HotspotService.Status.IDLE -> true
|
HotspotService.Status.IDLE -> true
|
||||||
HotspotService.Status.ACTIVE -> true
|
HotspotService.Status.ACTIVE -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
var serviceStarted: Boolean
|
var serviceStarted: Boolean
|
||||||
@Bindable get() = when (binder?.status) {
|
@Bindable get() = when (binder?.service?.status) {
|
||||||
HotspotService.Status.STARTING -> true
|
HotspotService.Status.STARTING -> true
|
||||||
HotspotService.Status.ACTIVE -> true
|
HotspotService.Status.ACTIVE -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
val binder = binder
|
val binder = binder
|
||||||
when (binder?.status) {
|
when (binder?.service?.status) {
|
||||||
HotspotService.Status.IDLE ->
|
HotspotService.Status.IDLE ->
|
||||||
if (value) ContextCompat.startForegroundService(this@MainActivity,
|
if (value) ContextCompat.startForegroundService(this@MainActivity,
|
||||||
Intent(this@MainActivity, HotspotService::class.java))
|
Intent(this@MainActivity, HotspotService::class.java))
|
||||||
@@ -44,7 +44,7 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val running get() = binder?.status == HotspotService.Status.ACTIVE
|
val running get() = binder?.service?.status == HotspotService.Status.ACTIVE
|
||||||
val ssid: String @Bindable get() = if (running) binder!!.service.group.networkName else ""
|
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 password: String @Bindable get() = if (running) binder!!.service.group.passphrase else ""
|
||||||
|
|
||||||
@@ -58,17 +58,19 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
notifyPropertyChanged(BR.password)
|
notifyPropertyChanged(BR.password)
|
||||||
adapter.fetchClients()
|
adapter.fetchClients()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val statusListener = broadcastReceiver { _, _ -> onStatusChanged() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientViewHolder(val binding: ClientBinding) : RecyclerView.ViewHolder(binding.root)
|
class ClientViewHolder(val binding: ListitemClientBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
inner class ClientAdapter : RecyclerView.Adapter<ClientViewHolder>() {
|
inner class ClientAdapter : RecyclerView.Adapter<ClientViewHolder>() {
|
||||||
private var owner: WifiP2pDevice? = null
|
private var owner: WifiP2pDevice? = null
|
||||||
private lateinit var clients: MutableCollection<WifiP2pDevice>
|
private lateinit var clients: MutableCollection<WifiP2pDevice>
|
||||||
private lateinit var arpCache: ArpCache
|
private lateinit var arpCache: ArpCache
|
||||||
|
|
||||||
fun fetchClients() {
|
fun fetchClients() {
|
||||||
val binder = binder!!
|
|
||||||
if (data.running) {
|
if (data.running) {
|
||||||
|
val binder = binder!!
|
||||||
owner = binder.service.group.owner
|
owner = binder.service.group.owner
|
||||||
clients = binder.service.group.clientList
|
clients = binder.service.group.clientList
|
||||||
arpCache = ArpCache(binder.service.downstream)
|
arpCache = ArpCache(binder.service.downstream)
|
||||||
@@ -77,7 +79,7 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
ClientViewHolder(ClientBinding.inflate(LayoutInflater.from(parent.context)))
|
ClientViewHolder(ListitemClientBinding.inflate(LayoutInflater.from(parent.context)))
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ClientViewHolder, position: Int) {
|
||||||
val device = when (position) {
|
val device = when (position) {
|
||||||
@@ -95,20 +97,30 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
override fun getItemCount() = if (owner == null) 0 else 1 + clients.size
|
override fun getItemCount() = if (owner == null) 0 else 1 + clients.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private val data = Data()
|
private val data = Data()
|
||||||
private val adapter = ClientAdapter()
|
private val adapter = ClientAdapter()
|
||||||
private var binder: HotspotService.HotspotBinder? = null
|
private var binder: HotspotService.HotspotBinder? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
|
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
||||||
binding.data = data
|
binding.data = data
|
||||||
binding.clients.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
binding.clients.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||||
val animator = DefaultItemAnimator()
|
val animator = DefaultItemAnimator()
|
||||||
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
|
animator.supportsChangeAnimations = false // prevent fading-in/out when rebinding
|
||||||
binding.clients.itemAnimator = animator
|
binding.clients.itemAnimator = animator
|
||||||
binding.clients.adapter = adapter
|
binding.clients.adapter = adapter
|
||||||
|
binding.toolbar.inflateMenu(R.menu.main)
|
||||||
|
binding.toolbar.setOnMenuItemClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
|
||||||
|
R.id.settings -> {
|
||||||
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -117,6 +129,7 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
|
onServiceDisconnected(null)
|
||||||
unbindService(this)
|
unbindService(this)
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
@@ -126,11 +139,14 @@ class MainActivity : AppCompatActivity(), ServiceConnection {
|
|||||||
binder.data = data
|
binder.data = data
|
||||||
this.binder = binder
|
this.binder = binder
|
||||||
data.onStatusChanged()
|
data.onStatusChanged()
|
||||||
|
LocalBroadcastManager.getInstance(this)
|
||||||
|
.registerReceiver(data.statusListener, intentFilter(HotspotService.STATUS_CHANGED))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
binder?.data = null
|
binder?.data = null
|
||||||
binder = null
|
binder = null
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(data.statusListener)
|
||||||
data.onStatusChanged()
|
data.onStatusChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
mobile/src/main/java/be/mygod/vpnhotspot/SettingsActivity.kt
Normal file
14
mobile/src/main/java/be/mygod/vpnhotspot/SettingsActivity.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
|
import android.databinding.DataBindingUtil
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AppCompatActivity
|
||||||
|
import be.mygod.vpnhotspot.databinding.ActivitySettingsBinding
|
||||||
|
|
||||||
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
DataBindingUtil.setContentView<ActivitySettingsBinding>(this, R.layout.activity_settings)
|
||||||
|
.toolbar.setNavigationOnClickListener({ finish() })
|
||||||
|
}
|
||||||
|
}
|
||||||
82
mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt
Normal file
82
mobile/src/main/java/be/mygod/vpnhotspot/SettingsFragment.kt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.support.customtabs.CustomTabsIntent
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.content.LocalBroadcastManager
|
||||||
|
import android.support.v7.preference.Preference
|
||||||
|
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
|
||||||
|
|
||||||
|
class SettingsFragment : PreferenceFragmentCompatDividers(), ServiceConnection {
|
||||||
|
private lateinit var service: Preference
|
||||||
|
private var binder: HotspotService.HotspotBinder? = null
|
||||||
|
private val statusListener = broadcastReceiver { _, _ -> onStatusChanged() }
|
||||||
|
private val customTabsIntent by lazy {
|
||||||
|
CustomTabsIntent.Builder().setToolbarColor(ContextCompat.getColor(activity!!, R.color.colorPrimary)).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.pref_settings)
|
||||||
|
service = findPreference("service")
|
||||||
|
findPreference("service.clean").setOnPreferenceClickListener {
|
||||||
|
noisySu("iptables -t nat -F PREROUTING",
|
||||||
|
"while iptables -D FORWARD -j vpnhotspot_fwd; do done",
|
||||||
|
"iptables -F vpnhotspot_fwd",
|
||||||
|
"iptables -X vpnhotspot_fwd",
|
||||||
|
"ip rule del lookup 62",
|
||||||
|
"ip route flush table 62")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
findPreference("misc.logcat").setOnPreferenceClickListener {
|
||||||
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
|
.setType("text/plain")
|
||||||
|
.putExtra(Intent.EXTRA_TEXT, Runtime.getRuntime().exec(arrayOf("logcat", "-d"))
|
||||||
|
.inputStream.bufferedReader().use { it.readText() })
|
||||||
|
startActivity(Intent.createChooser(intent, getString(R.string.abc_shareactionprovider_share_with)))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
findPreference("misc.source").setOnPreferenceClickListener {
|
||||||
|
customTabsIntent.launchUrl(activity, Uri.parse("https://github.com/Mygod/VPNHotspot"))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
findPreference("misc.donate").setOnPreferenceClickListener {
|
||||||
|
customTabsIntent.launchUrl(activity, Uri.parse("https://mygod.be/donate/"))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
val activity = activity!!
|
||||||
|
activity.bindService(Intent(activity, HotspotService::class.java), this, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
onServiceDisconnected(null)
|
||||||
|
activity!!.unbindService(this)
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
binder = service as HotspotService.HotspotBinder
|
||||||
|
onStatusChanged()
|
||||||
|
LocalBroadcastManager.getInstance(activity!!)
|
||||||
|
.registerReceiver(statusListener, intentFilter(HotspotService.STATUS_CHANGED))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
LocalBroadcastManager.getInstance(activity!!).unregisterReceiver(statusListener)
|
||||||
|
binder = null
|
||||||
|
service.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStatusChanged() {
|
||||||
|
service.isEnabled = binder!!.service.status == HotspotService.Status.IDLE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
fun createIntentFilter(vararg actions: String): IntentFilter {
|
fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun intentFilter(vararg actions: String): IntentFilter {
|
||||||
val result = IntentFilter()
|
val result = IntentFilter()
|
||||||
actions.forEach { result.addAction(it) }
|
actions.forEach { result.addAction(it) }
|
||||||
return result
|
return result
|
||||||
|
|||||||
9
mobile/src/main/res/drawable/ic_action_settings.xml
Normal file
9
mobile/src/main/res/drawable/ic_action_settings.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||||
|
</vector>
|
||||||
9
mobile/src/main/res/drawable/ic_navigation_close.xml
Normal file
9
mobile/src/main/res/drawable/ic_navigation_close.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
</vector>
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
android:layout_column="2"
|
android:layout_column="2"
|
||||||
android:layout_row="0"
|
android:layout_row="0"
|
||||||
android:text="@{data.ssid}"
|
android:text="@{data.ssid}"
|
||||||
|
android:textIsSelectable="true"
|
||||||
tools:text="DIRECT-rAnd0m"/>
|
tools:text="DIRECT-rAnd0m"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
android:layout_column="2"
|
android:layout_column="2"
|
||||||
android:layout_row="1"
|
android:layout_row="1"
|
||||||
android:text="@{data.password}"
|
android:text="@{data.password}"
|
||||||
|
android:textIsSelectable="true"
|
||||||
tools:text="p4ssW0rd"/>
|
tools:text="p4ssW0rd"/>
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
|
|
||||||
@@ -101,6 +103,6 @@
|
|||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
tools:listitem="@layout/client"/>
|
tools:listitem="@layout/listitem_client"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
||||||
26
mobile/src/main/res/layout/activity_settings.xml
Normal file
26
mobile/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:title="@string/title_activity_settings"
|
||||||
|
app:navigationIcon="@drawable/ic_navigation_close"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
<fragment
|
||||||
|
class="be.mygod.vpnhotspot.SettingsFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:id="@+id/preference"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@{device.deviceAddress}"
|
android:text="@{device.deviceAddress}"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||||
|
android:textIsSelectable="true"
|
||||||
tools:text="xx:xx:xx:xx:xx:xx"/>
|
tools:text="xx:xx:xx:xx:xx:xx"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="@{ipAddress == null ? View.GONE : View.VISIBLE}"
|
android:visibility="@{ipAddress == null ? View.GONE : View.VISIBLE}"
|
||||||
android:text="@{ipAddress}"
|
android:text="@{ipAddress}"
|
||||||
|
android:textIsSelectable="true"
|
||||||
tools:text="192.168.49.123"/>
|
tools:text="192.168.49.123"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
||||||
9
mobile/src/main/res/menu/main.xml
Normal file
9
mobile/src/main/res/menu/main.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/settings"
|
||||||
|
android:icon="@drawable/ic_action_settings"
|
||||||
|
android:title="@string/title_activity_settings"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
</menu>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">VPN Hotspot</string>
|
<string name="app_name">VPN Hotspot</string>
|
||||||
|
<string name="title_activity_settings">Settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<style name="AppTheme" parent="PreferenceFixTheme.Light.NoActionBar">
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
|||||||
41
mobile/src/main/res/xml/pref_settings.xml
Normal file
41
mobile/src/main/res/xml/pref_settings.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="service"
|
||||||
|
android:title="Service">
|
||||||
|
<AutoSummaryEditTextPreference
|
||||||
|
android:key="service.upstream"
|
||||||
|
android:title="Upstream interface"
|
||||||
|
android:summary="%s"
|
||||||
|
android:defaultValue="tun0"/>
|
||||||
|
<AutoSummaryEditTextPreference
|
||||||
|
android:key="service.route"
|
||||||
|
android:title="Wi-Fi Direct routing"
|
||||||
|
android:summary="%s"
|
||||||
|
android:defaultValue="192.168.49.0/24"/>
|
||||||
|
<AutoSummaryEditTextPreference
|
||||||
|
android:key="service.dns"
|
||||||
|
android:title="Downstream DNS server:port"
|
||||||
|
android:summary="%s"
|
||||||
|
android:defaultValue="8.8.8.8:53"/>
|
||||||
|
<Preference
|
||||||
|
android:key="service.clean"
|
||||||
|
android:title="Clean up"
|
||||||
|
android:summary="Remove routing rules"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="Misc">
|
||||||
|
<Preference
|
||||||
|
android:key="misc.logcat"
|
||||||
|
android:title="Export logcat"
|
||||||
|
android:summary="Such useful very wow"/>
|
||||||
|
<Preference
|
||||||
|
android:key="misc.source"
|
||||||
|
android:title="View on GitHub"
|
||||||
|
android:summary="Star, submit issues and contribute"/>
|
||||||
|
<Preference
|
||||||
|
android:key="misc.donate"
|
||||||
|
android:title="Donate"
|
||||||
|
android:summary="I love money"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
</PreferenceScreen>
|
||||||
Reference in New Issue
Block a user