refactor event service dispatch

This commit is contained in:
Kr328 committed 2019-12-16 18:57:23 +08:00
1 parent 0bd1fcf51b
commit eccc9bd886
21 files changed
+536 -566

No files matched your search

@@ -6,7 +6,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import com.github.kr328.clash.core.event.*
import com.github.kr328.clash.core.model.GeneralPacket
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.utils.ByteFormatter
import com.github.kr328.clash.service.TunService
import com.github.kr328.clash.utils.ServiceUtils
@@ -113,13 +113,13 @@ class MainActivity : BaseActivity() {
runOnUiThread {
when ( general.mode ) {
GeneralPacket.Mode.DIRECT ->
General.Mode.DIRECT ->
activity_main_clash_proxies_summary.text =
getText(R.string.clash_proxy_manage_summary_direct)
GeneralPacket.Mode.GLOBAL ->
General.Mode.GLOBAL ->
activity_main_clash_proxies_summary.text =
getText(R.string.clash_proxy_manage_summary_global)
GeneralPacket.Mode.RULE ->
General.Mode.RULE ->
activity_main_clash_proxies_summary.text =
getText(R.string.clash_proxy_manage_summary_rule)
}
-7
View File
@@ -173,11 +173,4 @@ open class GolangBindTask : DefaultTask() {
if (process.waitFor() != 0)
throw GradleException("Run command $this failure")
}
private fun String.exe(): String {
return if (Os.isFamily(Os.FAMILY_WINDOWS))
"$this.exe"
else
this
}
}
@@ -1,3 +0,0 @@
import java.io.File
data class GolangBuildOptions(val sourceDir: File, val outputDir: File, val platform: String)
-92
View File
@@ -1,92 +0,0 @@
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.GradleException
import org.gradle.api.tasks.Exec
import java.io.File
import java.io.FileReader
import java.util.Properties
open class GolangBuildTask : Exec() {
private lateinit var options: GolangBuildOptions
private lateinit var abi: String
override fun exec() {
val toolchainRoot = findAndroidNdkPath().resolve("toolchains/llvm/prebuilt/${detectOsType()}/bin")
val linkerPrefix = toolChainPrefix()
val compilerPrefix = linkerPrefix + options.platform
if ( !toolchainRoot.exists() )
throw GradleException("Compiler not found")
workingDir = options.sourceDir
environment.put("GOARCH", golangArch())
environment.put("GOOS", "android")
environment.put("CGO_ENABLED", "1")
environment.put("GOPATH", project.buildDir.resolve("intermediates/gopath").absolutePath)
environment.put("CXX", toolchainRoot.resolve(compilerPrefix + "-clang++".cmd()).absolutePath)
environment.put("CC", toolchainRoot.resolve(compilerPrefix + "-clang".cmd()).absolutePath)
environment.put("LD", toolchainRoot.resolve(linkerPrefix + "ld".exe()).absolutePath)
commandLine = listOf("go".exe(), "build", "-o", options.outputDir.resolve("$abi/libclash.so").absolutePath)
super.exec()
}
fun setOptions(options: GolangBuildOptions, abi: String) {
this.options = options
this.abi = abi
}
private fun findAndroidNdkPath(): File {
val properties = FileReader(project.rootProject.file("local.properties")).use {
Properties().apply { load(it) }
}
return properties.getProperty("ndk.dir")?.let { File(it) }?.takeIf { it.exists() }
?: throw GradleException("Android NDK not found.")
}
private fun detectOsType(): String {
return when {
Os.isFamily(Os.FAMILY_WINDOWS) -> "windows-x86_64"
Os.isFamily(Os.FAMILY_MAC) -> "darwin-x86_64"
Os.isFamily(Os.FAMILY_UNIX) -> "linux-x86_64"
else -> throw GradleException("Unsupported Build OS ${System.getenv("os.name")}")
}
}
private fun golangArch(): String {
return when (abi) {
"armeabi-v7a" -> "arm"
"arm64-v8a" -> "arm64"
"x86" -> "i686"
"x86_64" -> "amd64"
else -> throw GradleException("Unsupported arch $abi")
}
}
private fun toolChainPrefix(): String {
return when (abi) {
"armeabi-v7a" -> "arm-linux-androideabi"
"arm64-v8a" -> "aarch64-linux-android"
"x86" -> "i686-linux-android"
"x86_64" -> "x86_64-linux-android"
else -> throw GradleException("Unsupported arch $abi")
}
}
private fun String.exe(): String {
return if ( Os.isFamily(Os.FAMILY_WINDOWS) )
"$this.exe"
else
this
}
private fun String.cmd(): String {
return if ( Os.isFamily(Os.FAMILY_WINDOWS) )
"$this.cmd"
else
this
}
}
+27
View File
@@ -0,0 +1,27 @@
package bridge
import (
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/tunnel"
)
type TunnelGeneral struct {
Mode string
HTTPPort int
SocksPort int
RedirectPort int
}
func QueryGeneral() *TunnelGeneral {
result := &TunnelGeneral{}
g := executor.GetGeneral()
t := tunnel.Instance()
result.Mode = t.Mode().String()
result.HTTPPort = g.Port
result.SocksPort = g.SocksPort
result.RedirectPort = g.RedirPort
return result
}
+63
View File
@@ -0,0 +1,63 @@
package bridge
import (
"encoding/json"
"github.com/Dreamacro/clash/tunnel"
)
type Proxy struct {
Name string
Type string `json:"type"`
All []string `json:"all"`
Now string `json:"now"`
Delay int
}
type ProxyList struct {
proxies []*Proxy
}
func (p Proxy) GetAllLength() int {
return len(p.All)
}
func (p Proxy) GetAllElement(index int) string {
return p.All[index]
}
func (pl *ProxyList) GetProxiesLength() int {
return len(pl.proxies)
}
func (pl *ProxyList) GetProxiesElement(index int) *Proxy {
return pl.proxies[index]
}
func QueryAllProxies() (*ProxyList, error) {
ps := tunnel.Instance().Proxies()
result := make([]*Proxy, len(ps))
currentIndex := 0
for k, v := range ps {
current := &Proxy{
Name: k,
Type: v.Type().String(),
All: []string{},
Delay: int(v.LastDelay()),
}
data, err := v.MarshalJSON()
if err != nil {
return nil, err
}
json.Unmarshal(data, current)
result[currentIndex] = current
currentIndex++
}
return &ProxyList{proxies: result}, nil
}
@@ -1,194 +1,118 @@
package com.github.kr328.clash.core
import android.content.Context
import android.net.LocalSocket
import com.github.kr328.clash.core.event.BandwidthEvent
import com.github.kr328.clash.core.event.LogEvent
import bridge.Bridge
import com.github.kr328.clash.core.event.ProcessEvent
import com.github.kr328.clash.core.event.SpeedEvent
import com.github.kr328.clash.core.model.*
import com.github.kr328.clash.core.utils.Log
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.stringify
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.Proxy
import java.lang.IllegalStateException
class Clash(
context: Context,
clashDir: File,
controllerPath: File,
listener: (ProcessEvent) -> Unit
) : BaseClash(controllerPath) {
private val listener: (ProcessEvent) -> Unit
) {
companion object {
const val COMMAND_PING = 0
const val COMMAND_TUN_START = 1
const val COMMAND_TUN_STOP = 2
const val COMMAND_PROFILE_RELOAD = 4
const val COMMAND_QUERY_PROXIES = 5
const val COMMAND_PULL_SPEED = 6
const val COMMAND_PULL_LOG = 7
const val COMMAND_PULL_BANDWIDTH = 8
const val COMMAND_SET_PROXY = 9
const val COMMAND_QUERY_GENERAL = 10
const val COMMAND_URL_TEST = 11
const val PING_REPLY = 233
const val CLASH_DIR = "clash"
const val DEFAULT_URL_TEST_TIMEOUT = 5000
const val DEFAULT_URL_TEST_URL = "https://www.gstatic.com/generate_204"
}
val process = ClashProcess(context, clashDir, controllerPath, listener)
private var currentProcess = ProcessEvent.STOPPED
fun ping(): Boolean {
return runControlNoException(COMMAND_PING) { _, input, _ ->
input.readInt() == PING_REPLY
} ?: false
init {
val home = context.filesDir.resolve(CLASH_DIR)
home.mkdirs()
Bridge.init(home.absolutePath)
}
fun loadProfile(file: File, selected: Map<String, String>): List<String> {
return runControl(COMMAND_PROFILE_RELOAD) { _, input, output ->
output.writeString(
Json(JsonConfiguration.Stable)
.stringify(
LoadProfilePacket.Request.serializer(),
LoadProfilePacket.Request(file.absolutePath, selected)
)
)
val result = Json(JsonConfiguration.Stable)
.parse(LoadProfilePacket.Response.serializer(), input.readString())
if (result.error.isNotEmpty()) {
throw IOException(result.error)
}
return@runControl result.invalidSelected
}
fun getCurrentProcessStatus(): ProcessEvent {
return currentProcess
}
fun queryGeneral(): GeneralPacket {
return runCatching {
runControl(COMMAND_QUERY_GENERAL) { _, input, _ ->
Json(JsonConfiguration.Stable).parse(GeneralPacket.serializer(), input.readString())
}
}.getOrDefault(
GeneralPacket(
GeneralPacket.Ports(0, 0, 0, 0),
GeneralPacket.Mode.DIRECT
)
)
fun start() {
if ( currentProcess == ProcessEvent.STARTED )
return
currentProcess = ProcessEvent.STARTED
listener(currentProcess)
loadDefault()
}
fun queryProxies(): RawProxyPacket {
return runControl(COMMAND_QUERY_PROXIES) { _, input, _ ->
val data = input.readString()
fun stop() {
if ( currentProcess == ProcessEvent.STOPPED )
return
Json(JsonConfiguration.Stable.copy(strictMode = false))
.parse(RawProxyPacket.serializer(), data)
}
currentProcess = ProcessEvent.STOPPED
listener(currentProcess)
loadDefault()
}
fun setSelectProxy(key: String, value: String) {
runControl(COMMAND_SET_PROXY) { _, input, output ->
output.writeString(
Json(JsonConfiguration.Stable)
.stringify(
SetProxyPacket.Request.serializer(),
SetProxyPacket.Request(key, value)
)
)
fun startTunDevice(fd: Int, mtu: Int, gateway: String, dns: String, onSocket: (Int) -> Unit) {
enforceStarted()
val response = Json(JsonConfiguration.Stable).parse(
SetProxyPacket.Response.serializer(),
input.readString()
)
if (response.error.isNotEmpty())
throw IOException(response.error)
}
}
fun pullSpeedEvent(initial: (LocalSocket) -> Unit, callback: (SpeedEvent) -> Unit) {
runControlNoException(COMMAND_PULL_SPEED) { socket, input, _ ->
initial(socket)
while (!Thread.currentThread().isInterrupted) {
callback(
Json(JsonConfiguration.Stable).parse(
SpeedEvent.serializer(),
input.readString()
)
)
}
}
}
fun pullLogsEvent(initial: (LocalSocket) -> Unit, callback: (LogEvent) -> Unit) {
runControlNoException(COMMAND_PULL_LOG) { socket, input, _ ->
initial(socket)
while (!Thread.currentThread().isInterrupted) {
callback(
Json(JsonConfiguration.Stable).parse(
LogEvent.serializer(),
input.readString()
)
)
}
}
}
fun pullBandwidthEvent(initial: (LocalSocket) -> Unit, callback: (BandwidthEvent) -> Unit) {
runControlNoException(COMMAND_PULL_BANDWIDTH) { socket, input, _ ->
initial(socket)
while (!Thread.currentThread().isInterrupted) {
callback(
Json(JsonConfiguration.Stable).parse(
BandwidthEvent.serializer(),
input.readString()
)
)
}
}
}
fun startUrlTest(proxies: List<String>, callback: (String, Long) -> Unit) {
runControl(COMMAND_URL_TEST) { _, input, output ->
output.writeString(Json(JsonConfiguration.Stable)
.stringify(UrlTestPacket.Request.serializer(),
UrlTestPacket.Request(proxies, DEFAULT_URL_TEST_TIMEOUT, DEFAULT_URL_TEST_URL)))
while ( true ) {
val data = input.readString()
if ( data.isEmpty() )
return@runControl
val response = Json(JsonConfiguration.Stable)
.parse(UrlTestPacket.Response.serializer(), data)
callback(response.name, response.delay)
}
}
Log.i("Url test exited")
}
fun startTunDevice(fd: FileDescriptor, mtu: Int, dnsHijacking: Boolean) {
runControl(COMMAND_TUN_START) { socket, _, output ->
socket.setFileDescriptorsForSend(arrayOf(fd))
socket.outputStream.write(0)
socket.outputStream.flush()
output.writeInt(mtu)
output.writeInt(if ( dnsHijacking ) 1 else 0)
output.writeInt(0x243)
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), gateway, dns) {
onSocket(it.toInt())
}
}
fun stopTunDevice() {
runControlNoException(COMMAND_TUN_STOP)
Bridge.stopTunDevice()
}
fun loadProfile(path: String) {
enforceStarted()
Bridge.loadProfileFile(path)
}
fun queryProxies(): List<Proxy> {
enforceStarted()
val list = Bridge.queryAllProxies()
val result = mutableListOf<Proxy>()
for (i in 0..list.proxiesLength) {
val p = list.getProxiesElement(i)
val all = mutableListOf<String>()
for (index in 0..p.allLength) {
all.add(p.getAllElement(index))
}
result.add(
Proxy(
p.name,
Proxy.Type.fromString(p.type),
p.now,
all,
p.delay
)
)
}
return result
}
fun queryGeneral(): General {
val t = Bridge.queryGeneral()
return General(General.Mode.fromString(t.mode),
t.httpPort.toInt(), t.socksPort.toInt(), t.redirectPort.toInt())
}
private fun loadDefault() {
Bridge.loadProfileDefault()
}
private fun enforceStarted() {
if ( currentProcess == ProcessEvent.STOPPED )
throw IllegalStateException("Clash Stopped")
}
}
@@ -0,0 +1,65 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.serialization.Parcels
import kotlinx.serialization.Serializable
import java.lang.IllegalStateException
fun List<Proxy>.compress(): CompressedProxyList {
val nameMap = this.toList().mapIndexed { index, proxy ->
proxy.name to index
}.toMap()
val elements = this.map {
CompressedProxyList.Element(
name = nameMap[it.name] ?: throw IllegalStateException("Unknown proxy $it"),
type = it.type,
now = nameMap[it.now] ?: throw IllegalStateException("Unknown proxy $it"),
all = it.all.mapNotNull { name -> nameMap[name] },
delay = it.delay
)
}
return CompressedProxyList(nameMap.entries.associate { (k, v) -> v to k }, elements)
}
@Serializable
data class CompressedProxyList(val proxyName: Map<Int, String>, val elements: List<Element>): Parcelable {
@Serializable
data class Element(val name: Int,
val type: Proxy.Type,
val now: Int,
val all: List<Int>,
val delay: Long)
fun uncompress(): List<Proxy> {
return elements.map {
Proxy(
name = proxyName[it.name] ?: throw IllegalStateException("Unknown proxy $it"),
type = it.type,
now = proxyName[it.now] ?: throw IllegalStateException("Unknown proxy $it"),
all = it.all.mapNotNull { index -> proxyName[index] },
delay = it.delay
)
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcels.dump(serializer(), this, parcel)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CompressedProxyList> {
override fun createFromParcel(parcel: Parcel): CompressedProxyList {
return Parcels.load(serializer(), parcel)
}
override fun newArray(size: Int): Array<CompressedProxyList?> {
return arrayOfNulls(size)
}
}
}
@@ -5,25 +5,35 @@ import android.os.Parcelable
import com.github.kr328.clash.core.serialization.Parcels
import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor
import java.lang.IllegalArgumentException
import kotlin.IllegalArgumentException
@Serializable
data class GeneralPacket(val ports: Ports, val mode: Mode) : Parcelable {
data class General(val mode: Mode, val http: Int, val socks: Int, val redirect: Int) : Parcelable {
@Serializable(with = ModeSerializer::class)
enum class Mode {
DIRECT, GLOBAL, RULE
}
DIRECT, GLOBAL, RULE;
@Serializable
data class Ports(val http: Int, val socks: Int, val redirect: Int, val randomHttp: Int)
class ModeSerializer : KSerializer<Mode> {
companion object {
const val MODE_DIRECT = 1
const val MODE_GLOBAL = 2
const val MODE_RULE = 3
override fun toString(): String {
return when ( this ) {
DIRECT -> "Direct"
GLOBAL -> "Global"
RULE -> "Rule"
}
}
companion object {
fun fromString(mode: String): Mode {
return when ( mode ) {
"Direct" -> DIRECT
"Global" -> GLOBAL
"Rule" -> RULE
else -> throw IllegalArgumentException("Invalid mode $mode")
}
}
}
}
class ModeSerializer : KSerializer<Mode> {
override val descriptor: SerialDescriptor
get() = StringDescriptor
@@ -53,13 +63,20 @@ data class GeneralPacket(val ports: Ports, val mode: Mode) : Parcelable {
return 0
}
companion object CREATOR : Parcelable.Creator<GeneralPacket> {
override fun createFromParcel(parcel: Parcel): GeneralPacket {
return Parcels.load(serializer(), parcel)
}
companion object {
const val MODE_DIRECT = 1
const val MODE_GLOBAL = 2
const val MODE_RULE = 3
override fun newArray(size: Int): Array<GeneralPacket?> {
return arrayOfNulls(size)
@JvmField
val CREATOR = object: Parcelable.Creator<General> {
override fun createFromParcel(parcel: Parcel): General {
return Parcels.load(serializer(), parcel)
}
override fun newArray(size: Int): Array<General?> {
return arrayOfNulls(size)
}
}
}
}
@@ -1,10 +0,0 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
class LoadProfilePacket {
@Serializable
data class Request(val path: String, val selected: Map<String, String>)
@Serializable
data class Response(val error: String, val invalidSelected: List<String>)
}
@@ -0,0 +1,80 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
@Serializable
data class Proxy(
val name: String,
val type: Type,
val now: String,
val all: List<String>,
val delay: Long
) {
enum class Type {
SELECT,
URL_TEST,
FALLBACK,
DIRECT,
REJECT,
SHADOWSOCKS,
SNELL,
SOCKS5,
HTTP,
VMESS,
LOAD_BALANCE,
UNKNOWN;
override fun toString(): String {
return when (this) {
SELECT -> TYPE_SELECT
URL_TEST -> TYPE_URL_TEST
FALLBACK -> TYPE_FALLBACK
DIRECT -> TYPE_DIRECT
REJECT -> TYPE_REJECT
SHADOWSOCKS -> TYPE_SHADOWSOCKS
SNELL -> TYPE_SNELL
SOCKS5 -> TYPE_SOCKS5
HTTP -> TYPE_HTTP
VMESS -> TYPE_VMESS
LOAD_BALANCE -> TYPE_LOAD_BALANCE
UNKNOWN -> TYPE_UNKNOWN
}
}
companion object {
fun fromString(type: String): Type {
return when (type) {
TYPE_SELECT -> SELECT
TYPE_URL_TEST -> URL_TEST
TYPE_FALLBACK -> FALLBACK
TYPE_DIRECT -> DIRECT
TYPE_REJECT -> REJECT
TYPE_SHADOWSOCKS -> SHADOWSOCKS
TYPE_SNELL -> SNELL
TYPE_SOCKS5 -> SOCKS5
TYPE_HTTP -> HTTP
TYPE_VMESS -> VMESS
TYPE_LOAD_BALANCE -> LOAD_BALANCE
TYPE_UNKNOWN -> UNKNOWN
else -> UNKNOWN
}
}
}
}
companion object {
private const val TYPE_SELECT = "Selector"
private const val TYPE_URL_TEST = "URLTest"
private const val TYPE_FALLBACK = "Fallback"
private const val TYPE_DIRECT = "Direct"
private const val TYPE_REJECT = "Reject"
private const val TYPE_SHADOWSOCKS = "Shadowsocks"
private const val TYPE_SNELL = "Snell"
private const val TYPE_SOCKS5 = "Socks5"
private const val TYPE_HTTP = "Http"
private const val TYPE_VMESS = "Vmess"
private const val TYPE_LOAD_BALANCE = "LoadBalance"
private const val TYPE_UNKNOWN = "Unknown"
}
}
@@ -1,109 +0,0 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.serialization.Parcels
import kotlinx.serialization.Serializable
@Serializable
data class ProxyPacket(val mode: String, val proxies: Map<Int, Proxy>): Parcelable {
@Serializable
data class Proxy(val name: String, val type: Type, val now: Int, val all: List<Int>, val delay: Long)
enum class Type {
SELECT,
URL_TEST,
FALLBACK,
DIRECT,
REJECT,
SHADOWSOCKS,
SNELL,
SOCKS5,
HTTP,
VMESS,
LOAD_BALANCE,
UNKNOWN;
override fun toString(): String {
return when ( this ) {
SELECT -> "Select"
URL_TEST -> "UrlTest"
FALLBACK -> "Fallback"
DIRECT -> "Direct"
REJECT -> "Reject"
SHADOWSOCKS -> "Shadowsocks"
SNELL -> "Snell"
SOCKS5 -> "Socks5"
HTTP -> "HTTP"
VMESS -> "Vmess"
LOAD_BALANCE -> "LoadBalance"
UNKNOWN -> "Unknown"
}
}
}
companion object {
private const val TYPE_SELECT = "Selector"
private const val TYPE_URL_TEST = "URLTest"
private const val TYPE_FALLBACK = "Fallback"
private const val TYPE_DIRECT = "Direct"
private const val TYPE_REJECT = "Reject"
private const val TYPE_SHADOWSOCKS = "Shadowsocks"
private const val TYPE_SNELL = "Snell"
private const val TYPE_SOCKS5 = "Socks5"
private const val TYPE_HTTP = "Http"
private const val TYPE_VMESS = "Vmess"
private const val TYPE_LOAD_BALANCE = "LoadBalance"
private const val TYPE_UNKNOWN = "Unknown"
fun fromRawProxy(rawProxy: RawProxyPacket): ProxyPacket {
val hashed = rawProxy.proxies
.map { it.key to (it.key.hashCode() to it.value) }.toMap()
val proxies = hashed.map { entry ->
val type = when ( entry.value.second.type ) {
TYPE_SELECT -> Type.SELECT
TYPE_URL_TEST -> Type.URL_TEST
TYPE_FALLBACK -> Type.FALLBACK
TYPE_DIRECT -> Type.DIRECT
TYPE_REJECT -> Type.REJECT
TYPE_SHADOWSOCKS -> Type.SHADOWSOCKS
TYPE_SNELL -> Type.SNELL
TYPE_SOCKS5 -> Type.SOCKS5
TYPE_HTTP -> Type.HTTP
TYPE_VMESS -> Type.VMESS
TYPE_LOAD_BALANCE -> Type.LOAD_BALANCE
TYPE_UNKNOWN -> Type.UNKNOWN
else -> Type.UNKNOWN
}
val now = hashed[entry.value.second.now]?.first ?: 0
val all = entry.value.second.all.mapNotNull { hashed[it]?.first }
val delay = entry.value.second.history.firstOrNull()?.delay ?: 0
entry.value.first to Proxy(entry.key, type, now, all, delay)
}
return ProxyPacket(rawProxy.mode, proxies.toMap())
}
@JvmField
val CREATOR = object: Parcelable.Creator<ProxyPacket> {
override fun createFromParcel(parcel: Parcel): ProxyPacket {
return Parcels.load(serializer(), parcel)
}
override fun newArray(size: Int): Array<ProxyPacket?> {
return arrayOfNulls(size)
}
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcels.dump(serializer(), this, parcel)
}
override fun describeContents(): Int {
return 0
}
}
@@ -1,15 +0,0 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
@Serializable
data class RawProxyPacket(val mode: String, val proxies: Map<String, RawProxy>) {
@Serializable
data class RawProxy(val type: String,
val all: List<String> = emptyList(),
val now: String = "",
val history: List<History> = emptyList()) {
@Serializable
data class History(val delay: Long)
}
}
@@ -1,10 +0,0 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
class SetProxyPacket {
@Serializable
data class Request(val key: String, val value: String)
@Serializable
data class Response(val error: String)
}
@@ -1,11 +0,0 @@
package com.github.kr328.clash.core.model
import kotlinx.serialization.Serializable
class UrlTestPacket {
@Serializable
data class Request(val proxies: List<String>, val timeout: Int, val url: String)
@Serializable
data class Response(val name: String, val delay: Long)
}
@@ -1,4 +1,4 @@
package com.github.kr328.clash.core.model;
parcelable ProxyPacket;
parcelable GeneralPacket;
parcelable CompressedProxyList;
parcelable General;
@@ -23,7 +23,7 @@ interface IClashService {
void stop();
// Query
ProxyPacket queryAllProxies();
GeneralPacket queryGeneral();
CompressedProxyList queryAllProxies();
General queryGeneral();
void startUrlTest(in String[] proxies, IUrlTestCallback callback);
}
@@ -0,0 +1,37 @@
package com.github.kr328.clash.service
import android.content.Context
import com.github.kr328.clash.core.event.*
class ClashEventBridge(val service: ClashService): ClashProfileService.Master,
ClashEventService.Master, ClashEventPuller.Master {
val eventService: ClashEventService = ClashEventService(this)
override fun preformProfileChanged() {
eventService.performProfileChangedEvent(ProfileChangedEvent())
}
override fun acquireEvent(event: Int) {
service.acquireEvent(event)
}
override fun releaseEvent(event: Int) {
service.releaseEvent(event)
}
fun onProcessChanged(event: ProcessEvent) {
eventService.performProcessEvent(event)
}
override fun onLogPulled(event: LogEvent) {
eventService.performLogEvent(event)
}
override fun onSpeedPulled(event: SpeedEvent) {
eventService.performSpeedEvent(event)
}
override fun onBandwidthPulled(event: BandwidthEvent) {
eventService.performBandwidthEvent(event)
}
}
@@ -5,106 +5,40 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import bridge.Bridge
import com.github.kr328.clash.callback.IUrlTestCallback
import android.os.Binder
import android.os.IBinder
import android.os.IInterface
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.event.*
import com.github.kr328.clash.core.model.GeneralPacket
import com.github.kr328.clash.core.model.ProxyPacket
import com.github.kr328.clash.core.utils.Log
import java.io.File
import java.io.IOException
import java.util.concurrent.Executors
import javax.xml.transform.SourceLocator
import kotlin.concurrent.thread
class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
ClashProfileService.Master, ClashEventPuller.Master {
class ClashService : Service(), IClashEventObserver {
private val executor = Executors.newSingleThreadExecutor()
private val eventService = ClashEventService(this)
private val profileService = ClashProfileService(this, this)
private val settingService = ClashSettingService(this)
private val instance: ClashServiceImpl by lazy {
ClashServiceImpl(this)
}
private var processStatus = ProcessEvent.STOPPED
val events: ClashEventService
get() = instance.eventService
val clash: Clash
get() = instance.clash
//private lateinit var puller: ClashEventPuller
private lateinit var notification: ClashNotification
private val clashService = object : IClashService.Stub() {
override fun setSelectProxy(proxy: String?, selected: String?) {
require(proxy != null && selected != null)
try {
this@ClashService.profileService.setCurrentProfileProxy(proxy, selected)
} catch (e: IOException) {
Log.w("Set proxy failure", e)
this@ClashService.eventService.performErrorEvent(
ErrorEvent(ErrorEvent.Type.SET_PROXY_SELECTED, e.toString())
)
}
}
override fun queryGeneral(): GeneralPacket {
return GeneralPacket(GeneralPacket.Ports(0, 0, 0, 0), GeneralPacket.Mode.DIRECT)
}
override fun queryAllProxies(): ProxyPacket {
return ProxyPacket("Direct", emptyMap())
}
override fun startUrlTest(proxies: Array<out String>?, callback: IUrlTestCallback?) {
require(proxies != null && callback != null)
}
override fun start() {
if ( processStatus == ProcessEvent.STARTED )
return
processStatus = ProcessEvent.STARTED
this@ClashService.eventService.performProcessEvent(processStatus)
}
override fun stop() {
if ( processStatus == ProcessEvent.STOPPED )
return
processStatus = ProcessEvent.STOPPED
this@ClashService.eventService.performProcessEvent(processStatus)
}
override fun getEventService(): IClashEventService {
return this@ClashService.eventService
}
override fun getProfileService(): IClashProfileService {
return this@ClashService.profileService
}
override fun getSettingService(): IClashSettingService {
return this@ClashService.settingService
}
override fun getCurrentProcessStatus(): ProcessEvent {
return processStatus
}
}
private val screenReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_SCREEN_ON ->
eventService.registerEventObserver(
instance.eventService.registerEventObserver(
ClashService::class.java.name,
this@ClashService,
intArrayOf(Event.EVENT_SPEED)
)
Intent.ACTION_SCREEN_OFF ->
eventService.registerEventObserver(
instance.eventService.registerEventObserver(
ClashService::class.java.name,
this@ClashService,
intArrayOf()
@@ -113,27 +47,32 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
}
}
override fun acquireEvent(event: Int) {
if (processStatus == ProcessEvent.STOPPED)
private fun onClashProcessChanged(event: ProcessEvent) {
instance.eventService.performProcessEvent(event)
}
fun acquireEvent(event: Int) {
if ( instance.clash.getCurrentProcessStatus() == ProcessEvent.STOPPED )
return
}
override fun releaseEvent(event: Int) {
if (processStatus == ProcessEvent.STOPPED)
fun releaseEvent(event: Int) {
if ( instance.clash.getCurrentProcessStatus() == ProcessEvent.STOPPED )
return
}
override fun onCreate() {
super.onCreate()
Bridge.init(filesDir.resolve("clash").absolutePath)
//puller = ClashEventPuller(clash, this)
notification = ClashNotification(this)
eventService.registerEventObserver(
instance.eventService.registerEventObserver(
ClashService::class.java.name,
this@ClashService,
intArrayOf(Event.EVENT_SPEED)
@@ -148,20 +87,19 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
clashService.start()
instance.clash.start()
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
return clashService
return instance
}
override fun onDestroy() {
clashService.stop()
instance.clash.stop()
executor.shutdown()
eventService.shutdown()
unregisterReceiver(screenReceiver)
@@ -179,17 +117,15 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
notification.show()
eventService.recastEventRequirement()
instance.eventService.recastEventRequirement()
}
ProcessEvent.STOPPED -> {
eventService.performSpeedEvent(SpeedEvent(0, 0))
eventService.performBandwidthEvent(BandwidthEvent(0))
instance.eventService.performSpeedEvent(SpeedEvent(0, 0))
instance.eventService.performBandwidthEvent(BandwidthEvent(0))
notification.cancel()
stopSelf()
Bridge.loadProfileDefault()
}
}
@@ -198,28 +134,28 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
private fun reloadProfile() {
executor.submit {
if ( processStatus != ProcessEvent.STARTED)
if ( clash.getCurrentProcessStatus() != ProcessEvent.STARTED)
return@submit
val active = profileService.queryActiveProfile()
val active = instance.profileService.queryActiveProfile()
if (active == null) {
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.PROFILE_LOAD, "No profile activated"))
clashService.stop()
sendError(ErrorEvent.Type.PROFILE_LOAD, "No profile activated")
clash.stop()
return@submit
}
Log.i("Loading profile ${active.file}")
try {
Bridge.loadProfileFile(active.file)
clash.loadProfile(active.file)
notification.setProfile(active.name)
eventService.performProfileReloadEvent(ProfileReloadEvent())
events.performProfileReloadEvent(ProfileReloadEvent())
} catch (e: Exception) {
clashService.stop()
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.PROFILE_LOAD, e.message ?: "Unknown"))
clash.stop()
sendError(ErrorEvent.Type.PROFILE_LOAD, e.message)
Log.w("Load profile failure", e)
}
}
@@ -233,22 +169,6 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
notification.setSpeed(event?.up ?: 0, event?.down ?: 0)
}
override fun preformProfileChanged() {
eventService.performProfileChangedEvent(ProfileChangedEvent())
}
override fun onLogPulled(event: LogEvent) {
eventService.performLogEvent(event)
}
override fun onSpeedPulled(event: SpeedEvent) {
eventService.performSpeedEvent(event)
}
override fun onBandwidthPulled(event: BandwidthEvent) {
eventService.performBandwidthEvent(event)
}
override fun onBandwidthEvent(event: BandwidthEvent?) {}
override fun onLogEvent(event: LogEvent?) {}
override fun onErrorEvent(event: ErrorEvent?) {}
@@ -257,4 +177,8 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
return this@ClashService
}
}
private fun sendError(type: ErrorEvent.Type, message: String?) {
events.performErrorEvent(ErrorEvent(type, message ?: "Unknown"))
}
}
@@ -0,0 +1,88 @@
package com.github.kr328.clash.service
import com.github.kr328.clash.callback.IUrlTestCallback
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.event.ErrorEvent
import com.github.kr328.clash.core.event.ProcessEvent
import com.github.kr328.clash.core.model.CompressedProxyList
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.compress
import com.github.kr328.clash.core.utils.Log
import java.io.IOException
class ClashServiceImpl(val clashService: ClashService) : IClashService.Stub() {
private val bridge: ClashEventBridge = ClashEventBridge(clashService)
val clash: Clash = Clash(clashService, bridge::onProcessChanged)
val profileService = ClashProfileService(clashService, bridge)
val settingService = ClashSettingService(clashService)
val eventService: ClashEventService
get() = bridge.eventService
override fun setSelectProxy(proxy: String?, selected: String?) {
require(proxy != null && selected != null)
try {
profileService.setCurrentProfileProxy(proxy, selected)
} catch (e: IOException) {
Log.w("Set proxy failure", e)
eventService.performErrorEvent(
ErrorEvent(ErrorEvent.Type.SET_PROXY_SELECTED, e.toString())
)
}
}
override fun queryGeneral(): General {
return clash.queryGeneral()
}
override fun queryAllProxies(): CompressedProxyList {
return try {
clash.queryProxies().compress()
}
catch (e: Exception) {
Log.w("Query proxies", e)
eventService.performErrorEvent(ErrorEvent(ErrorEvent.Type.QUERY_PROXY_FAILURE, e.message ?: "Unknown"))
CompressedProxyList(emptyMap(), emptyList())
}
}
override fun startUrlTest(proxies: Array<out String>?, callback: IUrlTestCallback?) {
require(proxies != null && callback != null)
}
override fun start() {
clash.start()
}
override fun stop() {
clash.stop()
}
override fun getEventService(): IClashEventService {
return eventService
}
override fun getProfileService(): IClashProfileService {
return profileService
}
override fun getSettingService(): IClashSettingService {
return settingService
}
override fun getCurrentProcessStatus(): ProcessEvent {
return clash.getCurrentProcessStatus()
}
fun getClashInstance(): Clash {
return clash
}
fun shutdown() {
eventService.shutdown()
}
}
@@ -143,6 +143,8 @@ class TunService : VpnService(), IClashEventObserver {
private fun Builder.addBypassPrivateRoute(): Builder {
// IPv4
if ( settings.isBypassPrivateNetwork ) {
Log.i("Bypass Private Network")
resources.getStringArray(R.array.bypass_private_route).forEach {
val address = it.split("/")
addRoute(address[0], address[1].toInt())