Initial commit
Hotspot works. VPN not yet.
This commit is contained in:
5
mobile/.gitignore
vendored
Normal file
5
mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/build
|
||||
|
||||
# tests aren't ready yet
|
||||
/src/androidTest
|
||||
/src/test
|
||||
43
mobile/build.gradle
Normal file
43
mobile/build.gradle
Normal file
@@ -0,0 +1,43 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
buildToolsVersion "27.0.3"
|
||||
compileSdkVersion 27
|
||||
defaultConfig {
|
||||
applicationId "be.mygod.vpnhotspot"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "0.0.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary true
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
pseudoLocalesEnabled true
|
||||
}
|
||||
release {
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
dataBinding.enabled = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt "com.android.databinding:compiler:$androidPluginVersion"
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "com.android.support:customtabs:$supportLibraryVersion"
|
||||
implementation "com.android.support:preference-v14:$supportLibraryVersion"
|
||||
implementation "com.takisoft.fix:preference-v7:$takisoftFixVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlinVersion"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
|
||||
}
|
||||
|
||||
kapt.generateStubs = true
|
||||
21
mobile/proguard-rules.pro
vendored
Normal file
21
mobile/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
29
mobile/src/main/AndroidManifest.xml
Normal file
29
mobile/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="be.mygod.vpnhotspot">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".HotspotService">
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
15
mobile/src/main/java/be/mygod/vpnhotspot/App.kt
Normal file
15
mobile/src/main/java/be/mygod/vpnhotspot/App.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.os.Build
|
||||
|
||||
class App : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (Build.VERSION.SDK_INT >= 26) getSystemService(NotificationManager::class.java)
|
||||
.createNotificationChannel(NotificationChannel(HotspotService.CHANNEL,
|
||||
"Hotspot Service", NotificationManager.IMPORTANCE_LOW))
|
||||
}
|
||||
}
|
||||
156
mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt
Normal file
156
mobile/src/main/java/be/mygod/vpnhotspot/HotspotService.kt
Normal file
@@ -0,0 +1,156 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
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.os.Binder
|
||||
import android.os.Looper
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
||||
class HotspotService : Service(), WifiP2pManager.ChannelListener {
|
||||
companion object {
|
||||
const val CHANNEL = "hotspot"
|
||||
private const val TAG = "HotspotService"
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
IDLE, STARTING, ACTIVE
|
||||
}
|
||||
|
||||
inner class HotspotBinder : Binder() {
|
||||
val service get() = this@HotspotService
|
||||
val status get() = this@HotspotService.status
|
||||
var data: MainActivity.Data? = null
|
||||
|
||||
fun shutdown() {
|
||||
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() = clean()
|
||||
override fun onFailure(reason: Int) {
|
||||
Toast.makeText(this@HotspotService, "Failed to remove P2P group (reason: $reason)",
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var p2pManager: WifiP2pManager
|
||||
private lateinit var channel: WifiP2pManager.Channel
|
||||
private lateinit var group: WifiP2pGroup
|
||||
private var receiver: BroadcastReceiver? = null
|
||||
private val binder = HotspotBinder()
|
||||
|
||||
private var status = Status.IDLE
|
||||
set(value) {
|
||||
field = value
|
||||
binder.data?.onStatusChanged()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent) = binder
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
p2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
|
||||
onChannelDisconnected()
|
||||
}
|
||||
|
||||
override fun onChannelDisconnected() {
|
||||
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (status != Status.IDLE) return START_NOT_STICKY
|
||||
status = Status.STARTING
|
||||
initReceiver()
|
||||
p2pManager.requestGroupInfo(channel, {
|
||||
when {
|
||||
it == null -> doStart()
|
||||
it.isGroupOwner -> doStart(it)
|
||||
else -> {
|
||||
Log.i(TAG, "Removing old group ($it)")
|
||||
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
||||
override fun onSuccess() = doStart()
|
||||
override fun onFailure(reason: Int) {
|
||||
Toast.makeText(this@HotspotService, "Failed to remove old P2P group (reason: $reason)",
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun doStart() {
|
||||
p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener, WifiP2pManager.GroupInfoListener {
|
||||
private fun shutdown() {
|
||||
startForeground(0, NotificationCompat.Builder(this@HotspotService, CHANNEL)
|
||||
.build())
|
||||
clean()
|
||||
}
|
||||
|
||||
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 {
|
||||
Log.w(TAG, "Unexpected group: $group")
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(reason: Int) {
|
||||
Toast.makeText(this@HotspotService, "Failed to create P2P group (reason: $reason)",
|
||||
Toast.LENGTH_SHORT).show()
|
||||
shutdown()
|
||||
}
|
||||
})
|
||||
}
|
||||
private fun doStart(group: WifiP2pGroup) {
|
||||
status = Status.ACTIVE
|
||||
this.group = group
|
||||
startForeground(1, NotificationCompat.Builder(this@HotspotService, CHANNEL)
|
||||
.setColor(ContextCompat.getColor(this@HotspotService, R.color.colorPrimary))
|
||||
.setContentTitle(group.networkName)
|
||||
.setSubText(group.passphrase)
|
||||
.setSmallIcon(R.drawable.ic_device_wifi_tethering)
|
||||
.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() {
|
||||
status = Status.IDLE
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (status != Status.IDLE) binder.shutdown()
|
||||
if (receiver != null) {
|
||||
unregisterReceiver(receiver)
|
||||
receiver = null
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
80
mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt
Normal file
80
mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.databinding.BaseObservable
|
||||
import android.databinding.Bindable
|
||||
import android.databinding.DataBindingUtil
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import be.mygod.vpnhotspot.databinding.MainActivityBinding
|
||||
|
||||
class MainActivity : AppCompatActivity(), ServiceConnection {
|
||||
inner class Data : BaseObservable() {
|
||||
val switchEnabled: Boolean
|
||||
@Bindable get() = when (binder?.status) {
|
||||
HotspotService.Status.IDLE -> true
|
||||
HotspotService.Status.ACTIVE -> true
|
||||
else -> false
|
||||
}
|
||||
var serviceStarted: Boolean
|
||||
@Bindable get() = when (binder?.status) {
|
||||
HotspotService.Status.STARTING -> true
|
||||
HotspotService.Status.ACTIVE -> true
|
||||
else -> false
|
||||
}
|
||||
set(value) {
|
||||
val binder = binder
|
||||
when (binder?.status) {
|
||||
HotspotService.Status.IDLE ->
|
||||
ContextCompat.startForegroundService(this@MainActivity,
|
||||
Intent(this@MainActivity, HotspotService::class.java))
|
||||
HotspotService.Status.ACTIVE -> binder.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStatusChanged() {
|
||||
notifyPropertyChanged(BR.switchEnabled)
|
||||
notifyPropertyChanged(BR.serviceStarted)
|
||||
}
|
||||
fun onBinderChanged() {
|
||||
onStatusChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var binding: MainActivityBinding
|
||||
private val data = Data()
|
||||
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
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
bindService(Intent(this, HotspotService::class.java), this, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
unbindService(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val binder = service as HotspotService.HotspotBinder
|
||||
binder.data = data
|
||||
this.binder = binder
|
||||
data.onBinderChanged()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
binder?.data = null
|
||||
binder = null
|
||||
}
|
||||
}
|
||||
9
mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt
Normal file
9
mobile/src/main/java/be/mygod/vpnhotspot/Utils.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.content.IntentFilter
|
||||
|
||||
fun createIntentFilter(vararg actions: String): IntentFilter {
|
||||
val result = IntentFilter()
|
||||
actions.forEach { result.addAction(it) }
|
||||
return result
|
||||
}
|
||||
@@ -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="#FFFFFFFF"
|
||||
android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z"/>
|
||||
</vector>
|
||||
103
mobile/src/main/res/layout/main_activity.xml
Normal file
103
mobile/src/main/res/layout/main_activity.xml
Normal file
@@ -0,0 +1,103 @@
|
||||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<data>
|
||||
<variable
|
||||
name="data"
|
||||
type="be.mygod.vpnhotspot.MainActivity.Data"/>
|
||||
</data>
|
||||
<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"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
android:id="@+id/toolbar">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switchService"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:enabled="@{data.switchEnabled}"
|
||||
android:checked="@{data.serviceStarted}"
|
||||
android:onCheckedChanged="@{(_, checked) -> data.setServiceStarted(checked)}"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"/>
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Network name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_column="1"
|
||||
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"
|
||||
tools:text="DIRECT-rAnd0m"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_row="1"
|
||||
android:text="Password"
|
||||
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"
|
||||
tools:text="p4ssW0rd"/>
|
||||
</GridLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="Connected devices"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#000"
|
||||
android:backgroundTint="?android:attr/textColorSecondary"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"/>
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
6
mobile/src/main/res/values/colors.xml
Normal file
6
mobile/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
3
mobile/src/main/res/values/strings.xml
Normal file
3
mobile/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">VPN Hotspot</string>
|
||||
</resources>
|
||||
11
mobile/src/main/res/values/styles.xml
Normal file
11
mobile/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user