split ui / service
This commit is contained in:
20 files changed
+126
-304
No files matched your search
+5
-9
@@ -11,7 +11,6 @@ android {
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -22,13 +21,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation project(":core")
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
implementation project(":service")
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.github.kr328.clash", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -19,19 +19,5 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".ClashService"
|
||||
android:exported="false"
|
||||
android:process=":clash" />
|
||||
<service
|
||||
android:name=".TunService"
|
||||
android:exported="false"
|
||||
android:process=":clash"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.github.kr328.clash;
|
||||
|
||||
import com.github.kr328.clash.core.ClashProcessStatus;
|
||||
|
||||
interface IClashObserver {
|
||||
void onStatusChanged(in ClashProcessStatus status);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// IClashService.aidl
|
||||
package com.github.kr328.clash;
|
||||
|
||||
import com.github.kr328.clash.IClashObserver;
|
||||
|
||||
interface IClashService {
|
||||
void start();
|
||||
void stop();
|
||||
void registerObserver(String id, boolean notifyCurrent, in IClashObserver observer);
|
||||
void unregisterObserver(String id);
|
||||
void startTunDevice(in ParcelFileDescriptor fd, int mtu);
|
||||
void stopTunDevice();
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.github.kr328.clash.core;
|
||||
|
||||
parcelable ClashProcessStatus;
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.RemoteException
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.ClashProcessStatus
|
||||
import java.io.IOException
|
||||
|
||||
class ClashService : Service() {
|
||||
companion object {
|
||||
private val TAG = "ClashForAndroid"
|
||||
}
|
||||
|
||||
private lateinit var clash: Clash
|
||||
|
||||
private var status = ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED)
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
for (kv in observers) {
|
||||
runCatching {
|
||||
kv.value.onStatusChanged(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
private val observers = mutableMapOf<String, IClashObserver>()
|
||||
|
||||
private inner class ClashServiceImpl : IClashService.Stub() {
|
||||
override fun registerObserver(id: String?, notifyCurrent: Boolean, observer: IClashObserver?) {
|
||||
if (id == null || observer == null)
|
||||
throw RemoteException()
|
||||
|
||||
observers[id] = observer
|
||||
|
||||
if ( notifyCurrent )
|
||||
observer.onStatusChanged(status)
|
||||
|
||||
|
||||
observer.asBinder().linkToDeath({
|
||||
observers.remove(id)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
override fun unregisterObserver(id: String?) {
|
||||
if (id == null)
|
||||
throw RemoteException()
|
||||
|
||||
observers.remove(id)
|
||||
}
|
||||
|
||||
override fun stopTunDevice() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
try {
|
||||
clash.start()
|
||||
} catch (e: IOException) {
|
||||
throw RemoteException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) {
|
||||
try {
|
||||
clash.startTunDevice(fd.fileDescriptor, mtu)
|
||||
} catch (e: IOException) {
|
||||
throw RemoteException(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
clash = Clash(
|
||||
this,
|
||||
filesDir.resolve("clash"),
|
||||
cacheDir.resolve("clash_controller")
|
||||
) {
|
||||
this.status = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return ClashServiceImpl()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
clash.stop()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.VpnService
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import com.github.kr328.clash.core.ClashProcessStatus
|
||||
|
||||
class TunService : VpnService() {
|
||||
companion object {
|
||||
// from https://github.com/shadowsocks/shadowsocks-android/blob/master/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
|
||||
private const val VPN_MTU = 1500
|
||||
private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1"
|
||||
private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2"
|
||||
private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1"
|
||||
private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2"
|
||||
}
|
||||
|
||||
private lateinit var fileDescriptor: ParcelFileDescriptor
|
||||
private val connection = object: ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val clash = IClashService.Stub.asInterface(service) ?: throw NullPointerException()
|
||||
|
||||
clash.start()
|
||||
clash.registerObserver("tun", false, object: IClashObserver.Stub() {
|
||||
override fun onStatusChanged(status: ClashProcessStatus?) {
|
||||
if ( status == null )
|
||||
return
|
||||
|
||||
when ( status.status ) {
|
||||
ClashProcessStatus.STATUS_STOPPED -> stopSelf()
|
||||
ClashProcessStatus.STATUS_STARTED -> clash.startTunDevice(fileDescriptor, VPN_MTU)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
fileDescriptor = Builder()
|
||||
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
|
||||
.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||
.addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
||||
.addDnsServer(PRIVATE_VLAN6_ROUTER)
|
||||
.addDisallowedApplication(packageName)
|
||||
.addRoute("0.0.0.0", 0)
|
||||
.addRoute("::", 0)
|
||||
.setMtu(VPN_MTU)
|
||||
.setBlocking(false)
|
||||
.establish() ?: throw NullPointerException("Unable to establish VPN")
|
||||
|
||||
bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE)
|
||||
|
||||
return Service.START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
fileDescriptor.close()
|
||||
|
||||
unbindService(connection)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,7 +1,10 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.60'
|
||||
ext {
|
||||
kotlin_version = '1.3.60'
|
||||
room_version = '2.2.1'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.github.kr328.clash.core.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation project(":core")
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
}
|
||||
Whitespace-only changes.
Vendored
+21
@@ -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
|
||||
@@ -0,0 +1,22 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.kr328.clash.service">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".VpnRequestActivity"
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<service
|
||||
android:name="ClashService"
|
||||
android:exported="false"
|
||||
android:process=":clash" />
|
||||
<service
|
||||
android:name="TunService"
|
||||
android:exported="false"
|
||||
android:process=":clash"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.Bundle
|
||||
|
||||
class VpnRequestActivity : Activity() {
|
||||
companion object {
|
||||
const val REQUEST_CODE = 124
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val intent = VpnService.prepare(this)
|
||||
|
||||
if ( intent == null )
|
||||
finish()
|
||||
else
|
||||
startActivityForResult(intent, REQUEST_CODE)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if ( requestCode == REQUEST_CODE && resultCode == RESULT_OK ) {
|
||||
startService(Intent(this, TunService::class.java))
|
||||
}
|
||||
else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Service</string>
|
||||
</resources>
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
include ':app', ':core'
|
||||
include ':app', ':core', ':service'
|
||||
rootProject.name='Clash'
|
||||
Reference in new issue
Block a user