diff --git a/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl index ec9a24f..7158770 100644 --- a/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl +++ b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl @@ -1,9 +1,13 @@ // IClashService.aidl package com.github.kr328.clash; +import com.github.kr328.clash.IClashObserver; + interface IClashService { void start(); 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); } diff --git a/app/src/main/aidl/com/github/kr328/clash/model/ClashStatus.aidl b/app/src/main/aidl/com/github/kr328/clash/model/ClashStatus.aidl index 4fdc488..f4fffc6 100644 --- a/app/src/main/aidl/com/github/kr328/clash/model/ClashStatus.aidl +++ b/app/src/main/aidl/com/github/kr328/clash/model/ClashStatus.aidl @@ -1,3 +1,3 @@ -package com.github.kr328.clash; +package com.github.kr328.clash.model; parcelable ClashStatus; \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/ClashService.kt b/app/src/main/java/com/github/kr328/clash/ClashService.kt index 757404b..ed029a7 100644 --- a/app/src/main/java/com/github/kr328/clash/ClashService.kt +++ b/app/src/main/java/com/github/kr328/clash/ClashService.kt @@ -4,14 +4,12 @@ import android.app.Service import android.content.Intent import android.net.Uri import android.os.IBinder -import android.os.Parcel import android.os.ParcelFileDescriptor import android.os.RemoteException -import android.util.Log import com.github.kr328.clash.core.Clash import com.github.kr328.clash.core.ClashProcess -import java.io.FileDescriptor -import kotlin.concurrent.thread +import com.github.kr328.clash.model.ClashStatus +import java.io.IOException class ClashService : Service() { companion object { @@ -19,9 +17,46 @@ class ClashService : Service() { } private lateinit var clash: Clash + + private var status = ClashProcess.Status.STOPPED + set(value) { + field = value + + for (kv in observers) { + runCatching { + kv.value.onStatusChanged(ClashStatus(status)) + } + } + } + private val observers = mutableMapOf() + private inner class ClashServiceImpl : IClashService.Stub() { + override fun registerObserver(id: String?, observer: IClashObserver?) { + if (id == null || observer == null) + throw RemoteException() + + observers[id] = observer + + observer.onStatusChanged(ClashStatus(status)) + + observer.asBinder().linkToDeath({ + observers.remove(id) + }, 0) + } + + override fun unregisterObserver(id: String?) { + if (id == null) + throw RemoteException() + + observers.remove(id) + } + override fun start() { - clash.start() + try { + clash.start() + } catch (e: IOException) { + throw RemoteException(e.message) + } } override fun stop() { @@ -29,21 +64,31 @@ class ClashService : Service() { } override fun loadProfile(url: Uri?) { - clash.loadProfile(url ?: throw RemoteException()) + try { + clash.loadProfile(url ?: throw RemoteException()) + } catch (e: IOException) { + throw RemoteException(e.message) + } } override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) { - clash.startTunDevice(fd.fileDescriptor, mtu) + try { + clash.startTunDevice(fd.fileDescriptor, mtu) + } catch (e: IOException) { + throw RemoteException(e.message) + } } } override fun onCreate() { super.onCreate() - clash = Clash(this, + clash = Clash( + this, filesDir.resolve("clash"), - cacheDir.resolve("clash_controller")) { - + cacheDir.resolve("clash_controller") + ) { + this.status = it } } diff --git a/app/src/main/java/com/github/kr328/clash/MainActivity.kt b/app/src/main/java/com/github/kr328/clash/MainActivity.kt index 85119f5..1b27a98 100644 --- a/app/src/main/java/com/github/kr328/clash/MainActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/MainActivity.kt @@ -19,27 +19,6 @@ class MainActivity : AppCompatActivity() { private const val VPN_REQUEST = 234 } - private val connection = object : ServiceConnection { - override fun onServiceDisconnected(name: ComponentName?) { - Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show() - } - - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - try { - val clash = IClashService.Stub.asInterface(service) - - clash.loadProfile(cacheDir.resolve("config.yaml").absolutePath) - - - } catch (e: Exception) { - runOnUiThread { - Toast.makeText(this@MainActivity, e.toString(), Toast.LENGTH_LONG).show() - } - } - } - - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -60,7 +39,7 @@ class MainActivity : AppCompatActivity() { } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - IClashService.Stub.asInterface(service)?.loadProfile(data?.dataString) + IClashService.Stub.asInterface(service)?.loadProfile(data?.data) VpnService.prepare(this@MainActivity)?.apply { startActivityForResult(this, VPN_REQUEST) diff --git a/app/src/main/java/com/github/kr328/clash/TunService.kt b/app/src/main/java/com/github/kr328/clash/TunService.kt index 4c69eca..b4b018f 100644 --- a/app/src/main/java/com/github/kr328/clash/TunService.kt +++ b/app/src/main/java/com/github/kr328/clash/TunService.kt @@ -8,6 +8,9 @@ import android.content.ServiceConnection 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 java.lang.NullPointerException import java.lang.RuntimeException class TunService : VpnService() { @@ -27,7 +30,20 @@ class TunService : VpnService() { } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - IClashService.Stub.asInterface(service)?.startTunDevice(fileDescriptor, VPN_MTU) ?: stopSelf() + val clash = IClashService.Stub.asInterface(service) ?: throw NullPointerException() + + clash.start() + clash.registerObserver("tun", object: IClashObserver.Stub() { + override fun onStatusChanged(status: ClashStatus?) { + if ( status == null ) + return + + when ( status.status ) { + ClashProcess.Status.STOPPED -> stopSelf() + ClashProcess.Status.STARTED -> clash.startTunDevice(fileDescriptor, VPN_MTU) + } + } + }) } } @@ -44,7 +60,7 @@ class TunService : VpnService() { .addRoute("::", 0) .setMtu(VPN_MTU) .setBlocking(false) - .establish() ?: throw RuntimeException("Unable to establish VPN") + .establish() ?: throw NullPointerException("Unable to establish VPN") bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE) @@ -53,6 +69,7 @@ class TunService : VpnService() { override fun onDestroy() { fileDescriptor.close() + unbindService(connection) super.onDestroy()