clash service
This commit is contained in:
14 files changed
+201
-167
No files matched your search
@@ -1,38 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.github.kr328.clash">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<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>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.github.kr328.clash">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<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>
|
||||
<provider
|
||||
android:authorities="${applicationId}.data"
|
||||
android:name=".DataProvider"
|
||||
android:exported="false"/>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.kr328.clash;
|
||||
|
||||
import com.github.kr328.clash.model.ClashStatus;
|
||||
import com.github.kr328.clash.core.ClashProcessStatus;
|
||||
|
||||
interface IClashObserver {
|
||||
void onStatusChanged(in ClashStatus status);
|
||||
void onStatusChanged(in ClashProcessStatus status);
|
||||
}
|
||||
@@ -8,6 +8,6 @@ interface IClashService {
|
||||
void stop();
|
||||
void registerObserver(String id, in IClashObserver observer);
|
||||
void unregisterObserver(String id);
|
||||
void loadProfile(in Uri path);
|
||||
void startTunDevice(in ParcelFileDescriptor fd, int mtu);
|
||||
void stopTunDevice();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.github.kr328.clash.core;
|
||||
|
||||
parcelable ClashProcessStatus;
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.github.kr328.clash.model;
|
||||
|
||||
parcelable ClashStatus;
|
||||
@@ -2,13 +2,11 @@ package com.github.kr328.clash
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
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.ClashProcess
|
||||
import com.github.kr328.clash.model.ClashStatus
|
||||
import com.github.kr328.clash.core.ClashProcessStatus
|
||||
import java.io.IOException
|
||||
|
||||
class ClashService : Service() {
|
||||
@@ -18,13 +16,13 @@ class ClashService : Service() {
|
||||
|
||||
private lateinit var clash: Clash
|
||||
|
||||
private var status = ClashProcess.Status.STOPPED
|
||||
private var status = ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED)
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
for (kv in observers) {
|
||||
runCatching {
|
||||
kv.value.onStatusChanged(ClashStatus(status))
|
||||
kv.value.onStatusChanged(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +35,7 @@ class ClashService : Service() {
|
||||
|
||||
observers[id] = observer
|
||||
|
||||
observer.onStatusChanged(ClashStatus(status))
|
||||
observer.onStatusChanged(status)
|
||||
|
||||
observer.asBinder().linkToDeath({
|
||||
observers.remove(id)
|
||||
@@ -51,6 +49,10 @@ class ClashService : Service() {
|
||||
observers.remove(id)
|
||||
}
|
||||
|
||||
override fun stopTunDevice() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
try {
|
||||
clash.start()
|
||||
@@ -63,14 +65,6 @@ class ClashService : Service() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun loadProfile(url: Uri?) {
|
||||
try {
|
||||
clash.loadProfile(url ?: throw RemoteException())
|
||||
} catch (e: IOException) {
|
||||
throw RemoteException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) {
|
||||
try {
|
||||
clash.startTunDevice(fd.fileDescriptor, mtu)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
|
||||
class DataProvider : ContentProvider() {
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
projection: Array<out String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?,
|
||||
sortOrder: String?
|
||||
): Cursor? {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun update(
|
||||
uri: Uri,
|
||||
values: ContentValues?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?
|
||||
): Int {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +1,26 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.VpnService
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_CODE = 233
|
||||
private const val VPN_REQUEST = 234
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
button.setOnClickListener {
|
||||
startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).setType("*/*"), REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
||||
Toast.makeText(this, intent?.dataString, Toast.LENGTH_LONG).show()
|
||||
|
||||
thread {
|
||||
bindService(Intent(this, ClashService::class.java), object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
IClashService.Stub.asInterface(service)?.loadProfile(data?.data)
|
||||
|
||||
VpnService.prepare(this@MainActivity)?.apply {
|
||||
startActivityForResult(this, VPN_REQUEST)
|
||||
} ?: startService(Intent(this@MainActivity, TunService::class.java))
|
||||
}
|
||||
|
||||
}, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
} else if (requestCode == VPN_REQUEST && resultCode == Activity.RESULT_OK) {
|
||||
startService(Intent(this, TunService::class.java))
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.VpnService
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_CODE = 233
|
||||
private const val VPN_REQUEST = 234
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,8 @@ import android.net.VpnService
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import com.github.kr328.clash.core.ClashProcess
|
||||
import com.github.kr328.clash.model.ClashStatus
|
||||
import com.github.kr328.clash.core.ClashStatus
|
||||
import java.lang.NullPointerException
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class TunService : VpnService() {
|
||||
companion object {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.github.kr328.clash.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.github.kr328.clash.core.ClashProcess
|
||||
|
||||
data class ClashStatus(val status: ClashProcess.Status) : Parcelable {
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
when ( status ) {
|
||||
ClashProcess.Status.STOPPED -> parcel.writeInt(0)
|
||||
ClashProcess.Status.STARTED -> parcel.writeInt(1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<ClashStatus> {
|
||||
override fun createFromParcel(parcel: Parcel): ClashStatus {
|
||||
return ClashStatus(readFromParcel(parcel))
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ClashStatus?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
private fun readFromParcel(parcel: Parcel): ClashProcess.Status {
|
||||
return when ( parcel.readInt() ) {
|
||||
0 -> ClashProcess.Status.STOPPED
|
||||
1 -> ClashProcess.Status.STARTED
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,16 +4,16 @@ import android.content.Context
|
||||
import android.net.LocalSocket
|
||||
import android.net.LocalSocketAddress
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import org.json.JSONObject
|
||||
import java.io.*
|
||||
|
||||
class Clash(private val context: Context,
|
||||
clashDir: File,
|
||||
private val controllerPath: File,
|
||||
listener: (Status) -> Unit) :
|
||||
ClashProcess(context, clashDir, controllerPath, listener) {
|
||||
class Clash(
|
||||
private val context: Context,
|
||||
clashDir: File,
|
||||
private val controllerPath: File,
|
||||
listener: (ClashProcessStatus) -> Unit
|
||||
) : ClashProcess(context, clashDir, controllerPath, listener) {
|
||||
companion object {
|
||||
const val COMMAND_PING = 0
|
||||
const val COMMAND_TUN_START = 1
|
||||
|
||||
@@ -11,7 +11,7 @@ import kotlin.concurrent.thread
|
||||
abstract class ClashProcess(private val context: Context,
|
||||
private val clashDir: File,
|
||||
private val controllerPath: File,
|
||||
private val listener: (Status) -> Unit) {
|
||||
private val listener: (ClashProcessStatus) -> Unit) {
|
||||
companion object {
|
||||
const val TAG = "ClashForAndroid"
|
||||
|
||||
@@ -23,10 +23,6 @@ abstract class ClashProcess(private val context: Context,
|
||||
private val CONTROLLER_ERROR_PATTERN = Regex("\\[CONTROLLER] ERROR=\\{(.+)\\}")
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
STARTED, STOPPED
|
||||
}
|
||||
|
||||
private var pid: Int = 0
|
||||
private var process: Process? = null
|
||||
|
||||
@@ -83,7 +79,7 @@ abstract class ClashProcess(private val context: Context,
|
||||
process = p
|
||||
pid = currentPid
|
||||
|
||||
listener(Status.STARTED)
|
||||
listener(ClashProcessStatus(ClashProcessStatus.STATUS_STARTED))
|
||||
|
||||
Log.i(TAG, "Clash started pid = $pid")
|
||||
|
||||
@@ -99,7 +95,7 @@ abstract class ClashProcess(private val context: Context,
|
||||
pid = -1
|
||||
}
|
||||
|
||||
listener(Status.STOPPED)
|
||||
listener(ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
data class ClashProcessStatus(val status: Int) : Parcelable {
|
||||
companion object {
|
||||
const val STATUS_STOPPED = 0
|
||||
const val STATUS_STARTED = 1
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object: Parcelable.Creator<ClashProcessStatus> {
|
||||
override fun createFromParcel(parcel: Parcel): ClashProcessStatus {
|
||||
return ClashProcessStatus(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ClashProcessStatus?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this(parcel.readInt())
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeInt(status)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.kr328.clash.core
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
data class ClashStatus(val status: Int) : Parcelable {
|
||||
companion object {
|
||||
const val STOPPED = 0
|
||||
const val IDLE = 1
|
||||
const val LOADING_PROFILE = 2
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object: Parcelable.Creator<ClashStatus> {
|
||||
override fun createFromParcel(parcel: Parcel): ClashStatus {
|
||||
return ClashStatus(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ClashStatus?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this(parcel.readInt())
|
||||
|
||||
override fun writeToParcel(dest: Parcel?, flags: Int) {
|
||||
dest?.writeInt(status)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user