From fa6ac580d05fbb0c3b39da8f3941401caaeca6f3 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Thu, 16 Jan 2020 23:42:10 +0800 Subject: [PATCH] [WIP] refactor service --- .../java/com/github/kr328/clash/core/Clash.kt | 63 +++-- .../kr328/clash/core/event/BandwidthEvent.kt | 2 +- .../github/kr328/clash/core/event/Event.kt | 4 +- .../kr328/clash/core/event/EventStream.kt | 21 ++ .../github/kr328/clash/core/event/LogEvent.kt | 2 +- .../kr328/clash/core/model/ProxyGroup.kt | 24 +- .../clash/core/serialization/MergedParcels.kt | 218 ++++++++++++++++++ .../clash/callback/IUrlTestCallback.aidl | 5 - .../clash/service/IClashEventObserver.aidl | 13 -- .../clash/service/IClashEventService.aidl | 8 - ...{IClashService.aidl => IClashManager.aidl} | 20 +- .../clash/service/IClashProfileService.aidl | 12 - .../clash/service/ipc/IPCParcelables.aidl | 5 + .../service/ipc/ParcelableCompletedFuture.kt | 60 +++++ .../kr328/clash/service/ipc/ParcelablePipe.kt | 89 +++++++ .../clash/service/ipc/ParcelableResult.kt | 30 +++ 16 files changed, 495 insertions(+), 81 deletions(-) create mode 100644 core/src/main/java/com/github/kr328/clash/core/event/EventStream.kt create mode 100644 core/src/main/java/com/github/kr328/clash/core/serialization/MergedParcels.kt delete mode 100644 service/src/main/aidl/com/github/kr328/clash/callback/IUrlTestCallback.aidl delete mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashEventObserver.aidl delete mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashEventService.aidl rename service/src/main/aidl/com/github/kr328/clash/service/{IClashService.aidl => IClashManager.aidl} (53%) delete mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashProfileService.aidl create mode 100644 service/src/main/aidl/com/github/kr328/clash/service/ipc/IPCParcelables.aidl create mode 100644 service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt create mode 100644 service/src/main/java/com/github/kr328/clash/service/ipc/ParcelablePipe.kt create mode 100644 service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index cfc7fe1..586ac7c 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -3,10 +3,7 @@ package com.github.kr328.clash.core import android.content.Context import bridge.Bridge import bridge.EventPoll -import com.github.kr328.clash.core.event.BandwidthEvent -import com.github.kr328.clash.core.event.LogEvent -import com.github.kr328.clash.core.event.ProcessEvent -import com.github.kr328.clash.core.event.TrafficEvent +import com.github.kr328.clash.core.event.* import com.github.kr328.clash.core.model.General import com.github.kr328.clash.core.model.Proxy import com.github.kr328.clash.core.model.ProxyGroup @@ -15,6 +12,7 @@ import com.github.kr328.clash.core.transact.ProxyCollectionImpl import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl import java.io.InputStream import java.lang.IllegalStateException +import java.util.concurrent.BrokenBarrierException import java.util.concurrent.CompletableFuture class Clash( @@ -41,7 +39,7 @@ class Clash( } fun start() { - if ( currentProcess == ProcessEvent.STARTED ) + if (currentProcess == ProcessEvent.STARTED) return currentProcess = ProcessEvent.STARTED @@ -52,7 +50,7 @@ class Clash( } fun stop() { - if ( currentProcess == ProcessEvent.STOPPED ) + if (currentProcess == ProcessEvent.STOPPED) return currentProcess = ProcessEvent.STOPPED @@ -86,7 +84,7 @@ class Clash( fun downloadProfile(url: String, output: String, baseDir: String): CompletableFuture { return DoneCallbackImpl().apply { - Bridge.downloadProfileAndCheck(url, output, baseDir,this) + Bridge.downloadProfileAndCheck(url, output, baseDir, this) } } @@ -133,30 +131,49 @@ class Clash( fun queryGeneral(): General { val t = Bridge.queryGeneral() - return General(General.Mode.fromString(t.mode), - t.httpPort.toInt(), t.socksPort.toInt(), t.redirectPort.toInt()) + return General( + General.Mode.fromString(t.mode), + t.httpPort.toInt(), t.socksPort.toInt(), t.redirectPort.toInt() + ) } - fun pollTraffic(onEvent: (TrafficEvent) -> Unit): Poll { - return Poll(Bridge.pollTraffic { down, up -> - onEvent(TrafficEvent(down, up)) - }) + fun openTrafficEvent(): EventStream { + return object: EventStream() { + val traffic = Bridge.pollTraffic { down, up -> + send(TrafficEvent(down, up)) + } + + override fun onClose() { + traffic.stop() + } + } } - fun pollBandwidth(onEvent: (BandwidthEvent) -> Unit): Poll { - return Poll(Bridge.pollBandwidth { - onEvent(BandwidthEvent(it)) - }) + fun openBandwidthEvent(): EventStream { + return object: EventStream() { + val bandwidth = Bridge.pollBandwidth { + send(BandwidthEvent(it)) + } + + override fun onClose() { + bandwidth.stop() + } + } } - fun pollLogs(onEvent: (LogEvent) -> Unit): Poll { - return Poll(Bridge.pollLogs { level, payload -> - onEvent(LogEvent(LogEvent.Level.fromString(level), payload)) - }) + fun openLogEvent(): EventStream { + return object: EventStream() { + val log = Bridge.pollLogs { level, payload -> + send(LogEvent(LogEvent.Level.fromString(level), payload)) + } + + override fun onClose() { + log.stop() + } + } } private fun enforceStarted() { - if ( currentProcess == ProcessEvent.STOPPED ) - throw IllegalStateException("Clash Stopped") + check(currentProcess != ProcessEvent.STOPPED) { "Clash Stopped" } } } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/event/BandwidthEvent.kt b/core/src/main/java/com/github/kr328/clash/core/event/BandwidthEvent.kt index 62560ce..313b7ba 100644 --- a/core/src/main/java/com/github/kr328/clash/core/event/BandwidthEvent.kt +++ b/core/src/main/java/com/github/kr328/clash/core/event/BandwidthEvent.kt @@ -6,7 +6,7 @@ import com.github.kr328.clash.core.serialization.Parcels import kotlinx.serialization.Serializable @Serializable -data class BandwidthEvent(val total: Long): Parcelable { +data class BandwidthEvent(val total: Long): Event { override fun writeToParcel(parcel: Parcel, flags: Int) { Parcels.dump(serializer(), this, parcel) } diff --git a/core/src/main/java/com/github/kr328/clash/core/event/Event.kt b/core/src/main/java/com/github/kr328/clash/core/event/Event.kt index 06a284b..08fa5e9 100644 --- a/core/src/main/java/com/github/kr328/clash/core/event/Event.kt +++ b/core/src/main/java/com/github/kr328/clash/core/event/Event.kt @@ -1,6 +1,8 @@ package com.github.kr328.clash.core.event -interface Event { +import android.os.Parcelable + +interface Event: Parcelable { companion object { const val EVENT_LOG = 1 const val EVENT_TRAFFIC = 3 diff --git a/core/src/main/java/com/github/kr328/clash/core/event/EventStream.kt b/core/src/main/java/com/github/kr328/clash/core/event/EventStream.kt new file mode 100644 index 0000000..275484e --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/event/EventStream.kt @@ -0,0 +1,21 @@ +package com.github.kr328.clash.core.event + +import android.os.Parcelable + +abstract class EventStream { + private var on: (T) -> Unit = {} + + fun onEvent(callback: (T) -> Unit) { + on = callback + } + + fun close() { + onClose() + } + + fun send(event: T) { + on(event) + } + + abstract fun onClose() +} \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt b/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt index fe98e5e..016af6a 100644 --- a/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt +++ b/core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.internal.StringDescriptor @Serializable data class LogEvent(val level: Level, val message: String, val time: Long = System.currentTimeMillis()) : - Event, Parcelable { + Event { companion object { const val DEBUG_VALUE = "debug" const val INFO_VALUE = "info" diff --git a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt index aa22ba8..57cfa2f 100644 --- a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt +++ b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt @@ -1,5 +1,9 @@ package com.github.kr328.clash.core.model +import android.os.Parcel +import android.os.Parcelable +import com.github.kr328.clash.core.serialization.MergedParcels +import com.github.kr328.clash.core.serialization.Parcels import kotlinx.serialization.Serializable @Serializable @@ -9,4 +13,22 @@ data class ProxyGroup( val delay: Long, val current: String, val proxies: List -) \ No newline at end of file +): Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + MergedParcels.dump(serializer(), this, parcel) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ProxyGroup { + return MergedParcels.load(serializer(), parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/serialization/MergedParcels.kt b/core/src/main/java/com/github/kr328/clash/core/serialization/MergedParcels.kt new file mode 100644 index 0000000..98f7ff3 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/serialization/MergedParcels.kt @@ -0,0 +1,218 @@ +package com.github.kr328.clash.core.serialization + +import android.os.Parcel +import kotlinx.serialization.* +import kotlinx.serialization.modules.EmptyModule +import kotlinx.serialization.modules.SerialModule + +object MergedParcels: AbstractSerialFormat(EmptyModule) { + fun dump(serializer: SerializationStrategy, obj: T, parcel: Parcel) { + val data = Parcel.obtain() + val encoder = ParcelsEncoder(data) + + try { + serializer.serialize(encoder, obj) + + data.setDataPosition(0) + + parcel.writeStringList(encoder.getStringList()) + parcel.appendFrom(data, 0, data.dataSize()) + } + finally { + data.recycle() + } + } + + fun load(deserializer: DeserializationStrategy, parcel: Parcel): T { + val strings = mutableListOf().apply { parcel.readStringList(this) } + return deserializer.deserialize(ParcelsDecoder(strings, parcel)) + } + + private class ParcelsEncoder(private val parcel: Parcel) : + Encoder, CompositeEncoder { + private val strings = mutableMapOf() + private var stringIndex = 0 + + fun getStringList(): List { + val result = mutableListOf() + strings.map { it.value to it.key } + .sortedBy { it.first } + .forEach { result.add(it.second) } + return result + } + + override val context: SerialModule + get() = EmptyModule + + override fun beginCollection( + desc: SerialDescriptor, + collectionSize: Int, + vararg typeParams: KSerializer<*> + ): CompositeEncoder { + encodeInt(collectionSize) + return super.beginCollection(desc, collectionSize, *typeParams) + } + + override fun encodeBooleanElement(desc: SerialDescriptor, index: Int, value: Boolean) = + encodeBoolean(value) + override fun encodeByteElement(desc: SerialDescriptor, index: Int, value: Byte) = + encodeByte(value) + override fun encodeCharElement(desc: SerialDescriptor, index: Int, value: Char) = + encodeChar(value) + override fun encodeDoubleElement(desc: SerialDescriptor, index: Int, value: Double) = + encodeDouble(value) + override fun encodeFloatElement(desc: SerialDescriptor, index: Int, value: Float) = + encodeFloat(value) + override fun encodeIntElement(desc: SerialDescriptor, index: Int, value: Int) = + encodeInt(value) + override fun encodeLongElement(desc: SerialDescriptor, index: Int, value: Long) = + encodeLong(value) + override fun encodeShortElement(desc: SerialDescriptor, index: Int, value: Short) = + encodeShort(value) + override fun encodeStringElement(desc: SerialDescriptor, index: Int, value: String) = + encodeString(value) + override fun encodeUnitElement(desc: SerialDescriptor, index: Int) = + encodeUnit() + + override fun encodeNonSerializableElement(desc: SerialDescriptor, index: Int, value: Any) = + throw IllegalArgumentException("Unsupported") + override fun encodeNullableSerializableElement( + desc: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T? + ) = encodeNullableSerializableValue(serializer, value) + override fun encodeSerializableElement( + desc: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T + ) = encodeSerializableValue(serializer, value) + + override fun beginStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> + ): CompositeEncoder = this + + override fun encodeBoolean(value: Boolean) = + parcel.writeByte(if ( value ) 1 else 0) + override fun encodeByte(value: Byte) = + parcel.writeByte(value) + override fun encodeChar(value: Char) = + parcel.writeInt(value.toInt()) + override fun encodeDouble(value: Double) = + parcel.writeDouble(value) + override fun encodeEnum(enumDescription: SerialDescriptor, ordinal: Int) = + parcel.writeInt(ordinal) + override fun encodeFloat(value: Float) = + parcel.writeFloat(value) + override fun encodeInt(value: Int) = + parcel.writeInt(value) + override fun encodeLong(value: Long) = + parcel.writeLong(value) + override fun encodeNotNullMark() = + encodeBoolean(true) + override fun encodeNull() = + encodeBoolean(false) + override fun encodeShort(value: Short) = + parcel.writeInt(value.toInt()) + override fun encodeUnit() {} + override fun encodeString(value: String) { + val index = strings.computeIfAbsent(value) { + stringIndex++ + } + parcel.writeInt(index) + } + } + + class ParcelsDecoder(private val strings: List, private val parcel: Parcel) : Decoder, CompositeDecoder { + override val context: SerialModule + get() = EmptyModule + override val updateMode: UpdateMode + get() = UpdateMode.BANNED + + override fun decodeElementIndex(desc: SerialDescriptor) = + CompositeDecoder.READ_ALL + override fun decodeCollectionSize(desc: SerialDescriptor) = + decodeInt() + + override fun decodeBooleanElement(desc: SerialDescriptor, index: Int) = + decodeBoolean() + override fun decodeByteElement(desc: SerialDescriptor, index: Int) = + decodeByte() + override fun decodeCharElement(desc: SerialDescriptor, index: Int) = + decodeChar() + override fun decodeDoubleElement(desc: SerialDescriptor, index: Int) = + decodeDouble() + override fun decodeFloatElement(desc: SerialDescriptor, index: Int) = + decodeFloat() + override fun decodeIntElement(desc: SerialDescriptor, index: Int) = + decodeInt() + override fun decodeShortElement(desc: SerialDescriptor, index: Int) = + decodeShort() + override fun decodeLongElement(desc: SerialDescriptor, index: Int) = + decodeLong() + override fun decodeStringElement(desc: SerialDescriptor, index: Int) = + decodeString() + override fun decodeUnitElement(desc: SerialDescriptor, index: Int) = + decodeUnit() + + override fun decodeNullableSerializableElement( + desc: SerialDescriptor, + index: Int, + deserializer: DeserializationStrategy + ) = decodeNullableSerializableValue(deserializer) + override fun decodeSerializableElement( + desc: SerialDescriptor, + index: Int, + deserializer: DeserializationStrategy + ) = decodeSerializableValue(deserializer) + + override fun updateNullableSerializableElement( + desc: SerialDescriptor, + index: Int, + deserializer: DeserializationStrategy, + old: T? + ) = updateNullableSerializableValue(deserializer, old) + + override fun updateSerializableElement( + desc: SerialDescriptor, + index: Int, + deserializer: DeserializationStrategy, + old: T + ) = updateSerializableValue(deserializer, old) + + override fun beginStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> + ): CompositeDecoder = this + + override fun decodeBoolean() = + parcel.readByte() != 0.toByte() + override fun decodeByte() = + parcel.readByte() + override fun decodeChar() = + parcel.readInt().toChar() + override fun decodeDouble() = + parcel.readDouble() + override fun decodeEnum(enumDescription: SerialDescriptor) = + parcel.readInt() + override fun decodeFloat() = + parcel.readFloat() + override fun decodeInt() = + parcel.readInt() + override fun decodeLong() = + parcel.readLong() + override fun decodeNotNullMark() = + decodeBoolean() + override fun decodeNull() = + null + override fun decodeShort() = + parcel.readInt().toShort() + override fun decodeUnit() {} + override fun decodeString() = + strings[parcel.readInt()] + } +} + + diff --git a/service/src/main/aidl/com/github/kr328/clash/callback/IUrlTestCallback.aidl b/service/src/main/aidl/com/github/kr328/clash/callback/IUrlTestCallback.aidl deleted file mode 100644 index 5e6b8fb..0000000 --- a/service/src/main/aidl/com/github/kr328/clash/callback/IUrlTestCallback.aidl +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.kr328.clash.callback; - -interface IUrlTestCallback { - void onResult(String proxy, long delay); -} diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashEventObserver.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashEventObserver.aidl deleted file mode 100644 index dfbab90..0000000 --- a/service/src/main/aidl/com/github/kr328/clash/service/IClashEventObserver.aidl +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.kr328.clash.service; - -import com.github.kr328.clash.core.event.Event; - -interface IClashEventObserver { - void onProcessEvent(in ProcessEvent event); - void onLogEvent(in LogEvent event); - void onErrorEvent(in ErrorEvent event); - void onTrafficEvent(in TrafficEvent event); - void onBandwidthEvent(in BandwidthEvent event); - void onProfileChanged(in ProfileChangedEvent event); - void onProfileReloaded(in ProfileReloadEvent event); -} diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashEventService.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashEventService.aidl deleted file mode 100644 index bb0beea..0000000 --- a/service/src/main/aidl/com/github/kr328/clash/service/IClashEventService.aidl +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.kr328.clash.service; - -import com.github.kr328.clash.service.IClashEventObserver; - -interface IClashEventService { - void registerEventObserver(String id, in IClashEventObserver observer, in int[] events); - void unregisterEventObserver(String id); -} diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl similarity index 53% rename from service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl rename to service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl index b8fd001..139b70d 100644 --- a/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl +++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl @@ -4,29 +4,17 @@ import com.github.kr328.clash.service.IClashEventObserver; import com.github.kr328.clash.service.IClashEventService; import com.github.kr328.clash.service.IClashProfileService; import com.github.kr328.clash.service.IClashSettingService; +import com.github.kr328.clash.service.ipc.IPCParcelables; import com.github.kr328.clash.callback.IUrlTestCallback; import com.github.kr328.clash.core.event.Event; import com.github.kr328.clash.core.model.Packet; -interface IClashService { - // Services - IClashEventService getEventService(); - IClashProfileService getProfileService(); - IClashSettingService getSettingService(); - - // Status - ProcessEvent getCurrentProcessStatus(); - +interface IClashManager { // Control - void setSelectProxy(String proxy, String selected); - void start(); - void stop(); + ParcelableCompletedFuture setSelectProxy(String proxy, String selected); // Query CompressedProxyList queryAllProxies(); General queryGeneral(); - void startUrlTest(in String[] proxies, IUrlTestCallback callback); - - // Utils - String checkProfileValid(in ParcelFileDescriptor pipe); + ParcelableCompletedFuture startUrlTest(String group); } diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileService.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileService.aidl deleted file mode 100644 index 986c083..0000000 --- a/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileService.aidl +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.kr328.clash.service; - -import com.github.kr328.clash.service.data.ClashProfileEntity; - -interface IClashProfileService { - ClashProfileEntity[] queryProfiles(); - void setActiveProfile(int id); - void addProfile(in ClashProfileEntity profile); - void removeProfile(int id); - void touchProfile(int id); - ClashProfileEntity queryActiveProfile(); -} diff --git a/service/src/main/aidl/com/github/kr328/clash/service/ipc/IPCParcelables.aidl b/service/src/main/aidl/com/github/kr328/clash/service/ipc/IPCParcelables.aidl new file mode 100644 index 0000000..16f4928 --- /dev/null +++ b/service/src/main/aidl/com/github/kr328/clash/service/ipc/IPCParcelables.aidl @@ -0,0 +1,5 @@ +package com.github.kr328.clash.service.ipc; + +parcelable ParcelablePipe; +parcelable ParcelableResult; +parcelable ParcelableCompletedFuture; diff --git a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt new file mode 100644 index 0000000..70c9560 --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableCompletedFuture.kt @@ -0,0 +1,60 @@ +package com.github.kr328.clash.service.ipc + +import android.os.Parcel +import android.os.Parcelable +import android.os.RemoteException +import java.util.concurrent.CompletableFuture + +class ParcelableCompletedFuture(private val pipe: ParcelablePipe = ParcelablePipe()) : + CompletableFuture(), Parcelable { + constructor(parcel: Parcel) : this( + parcel.readParcelable( + ParcelableCompletedFuture::class.java.classLoader + ) ?: throw NullPointerException() + ) { + pipe.onReceive { + val result = (it ?: throw NullPointerException()) as ParcelableResult + + if ( result.exception != null ) { + completeExceptionally(RemoteException(result.exception)) + } + else { + complete(result.data) + } + } + } + + override fun complete(value: Parcelable?): Boolean { + if ( super.complete(value) ) { + pipe.send(ParcelableResult(value, null)) + return true + } + return false + } + + override fun completeExceptionally(ex: Throwable?): Boolean { + if ( super.completeExceptionally(ex) ) { + pipe.send(ParcelableResult(null, ex?.message ?: "Unknown")) + return true + } + return false + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(pipe, 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ParcelableCompletedFuture { + return ParcelableCompletedFuture(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelablePipe.kt b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelablePipe.kt new file mode 100644 index 0000000..e175ebe --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelablePipe.kt @@ -0,0 +1,89 @@ +package com.github.kr328.clash.service.ipc + +import android.os.Binder +import android.os.IBinder +import android.os.Parcel +import android.os.Parcelable + +class ParcelablePipe() : Parcelable { + private inner class Slave: Binder() { + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + if ( code == TRANSACT_CODE_SEND_PARCELABLE ) { + receiveCallback(data.readParcelable(ParcelablePipe::class.java.classLoader)) + return true + } + return false + } + } + private inner class Master: Binder() { + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + if ( code == TRANSACT_CODE_SET_SLAVE ) { + slave = data.readStrongBinder() + return true + } + return false + } + } + + private var slave: IBinder? = null + private var receiveCallback: (Parcelable?) -> Unit = {} + + constructor(parcel: Parcel) : this() { + val master = parcel.readStrongBinder() + val data = Parcel.obtain() + + try { + data.writeStrongBinder(Slave()) + + master.transact(TRANSACT_CODE_SET_SLAVE, data, null, 0) + } + finally { + data.recycle() + } + } + + override fun writeToParcel(dest: Parcel?, flags: Int) { + if ( dest == null ) + return + + dest.writeStrongBinder(Master()) + } + + override fun describeContents(): Int = 0 + + fun send(parcelable: Parcelable?): Boolean { + val s = slave ?: return false + val data = Parcel.obtain() + + try { + data.writeParcelable(parcelable, 0) + + s.transact(TRANSACT_CODE_SEND_PARCELABLE, data, null, 0) + } + finally { + data.recycle() + } + + return true + } + + fun onReceive(callback: (Parcelable?) -> Unit) { + this.receiveCallback = callback + } + + companion object { + private const val TRANSACT_CODE_SET_SLAVE = 1 + private const val TRANSACT_CODE_SEND_PARCELABLE = 2 + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ParcelablePipe { + return ParcelablePipe(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt new file mode 100644 index 0000000..cd3b28f --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/ipc/ParcelableResult.kt @@ -0,0 +1,30 @@ +package com.github.kr328.clash.service.ipc + +import android.os.Parcel +import android.os.Parcelable + +data class ParcelableResult(val data: Parcelable?, val exception: String?): Parcelable { + constructor(parcel: Parcel) : this( + parcel.readParcelable(ParcelableResult::class.java.classLoader), + parcel.readString() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(data, 0) + parcel.writeString(exception) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ParcelableResult { + return ParcelableResult(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file