Add settings

This commit is contained in:
Mygod
2018-01-03 22:58:45 +08:00
parent 3e7fae95cf
commit 826f601301
17 changed files with 313 additions and 75 deletions

View File

@@ -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"

View File

@@ -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>

View File

@@ -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) }
} }

View File

@@ -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) {

View File

@@ -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()
} }
} }

View 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() })
}
}

View 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
}
}

View File

@@ -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

View 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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>