Fix some issues
This commit is contained in:
@@ -3,6 +3,7 @@ package com.follow.clash
|
||||
import com.follow.clash.common.ServiceDelegate
|
||||
import com.follow.clash.common.formatString
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.service.IAckInterface
|
||||
import com.follow.clash.service.ICallbackInterface
|
||||
import com.follow.clash.service.IEventInterface
|
||||
import com.follow.clash.service.IRemoteInterface
|
||||
@@ -44,8 +45,11 @@ object Service {
|
||||
return delegate.useService {
|
||||
it.invokeAction(
|
||||
data, object : ICallbackInterface.Stub() {
|
||||
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
|
||||
override fun onResult(
|
||||
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||
) {
|
||||
res.add(result ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
cb(res.formatString())
|
||||
}
|
||||
@@ -61,24 +65,24 @@ object Service {
|
||||
return delegate.useService {
|
||||
it.setEventListener(
|
||||
when (cb != null) {
|
||||
true -> object : IEventInterface.Stub() {
|
||||
override fun onEvent(
|
||||
id: String, data: ByteArray?, isSuccess: Boolean
|
||||
) {
|
||||
if (results[id] == null) {
|
||||
results[id] = mutableListOf()
|
||||
}
|
||||
results[id]?.add(data ?: byteArrayOf())
|
||||
if (isSuccess) {
|
||||
cb(results[id]?.formatString())
|
||||
results.remove(id)
|
||||
}
|
||||
true -> object : IEventInterface.Stub() {
|
||||
override fun onEvent(
|
||||
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
|
||||
) {
|
||||
if (results[id] == null) {
|
||||
results[id] = mutableListOf()
|
||||
}
|
||||
results[id]?.add(data ?: byteArrayOf())
|
||||
ack?.onAck()
|
||||
if (isSuccess) {
|
||||
cb(results[id]?.formatString())
|
||||
results.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
false -> null
|
||||
}
|
||||
)
|
||||
|
||||
false -> null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import com.follow.clash.common.QuickAction
|
||||
import com.follow.clash.common.quickIntent
|
||||
import com.follow.clash.common.Components
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.intent
|
||||
import com.follow.clash.common.toPendingIntent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -39,20 +40,22 @@ class TileService : TileService() {
|
||||
}
|
||||
|
||||
@SuppressLint("StartActivityAndCollapseDeprecated")
|
||||
private fun handleToggle() {
|
||||
val intent = QuickAction.TOGGLE.quickIntent
|
||||
private fun activityTransfer() {
|
||||
val intent = Components.TEMP_ACTIVITY.intent
|
||||
val pendingIntent = intent.toPendingIntent
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startActivityAndCollapse(pendingIntent)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityAndCollapse(intent)
|
||||
@Suppress("DEPRECATION") startActivityAndCollapse(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
handleToggle()
|
||||
activityTransfer()
|
||||
GlobalState.launch {
|
||||
State.handleToggleAction()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
|
||||
@@ -220,7 +220,6 @@ val Long.formatBytes: String
|
||||
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
|
||||
val allBytes = toByteArray(charset)
|
||||
val total = allBytes.size
|
||||
|
||||
val maxBytes = when {
|
||||
total <= 100 * 1024 -> total
|
||||
total <= 1024 * 1024 -> 64 * 1024
|
||||
|
||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -59,7 +60,9 @@ class ServiceDelegate<T>(
|
||||
withTimeout(timeoutMillis) {
|
||||
val state = serviceState.filterNotNull().first()
|
||||
state.first?.let {
|
||||
block(it)
|
||||
withContext(Dispatchers.Default) {
|
||||
block(it)
|
||||
}
|
||||
} ?: throw Exception(state.second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// IAckInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.IAckInterface;
|
||||
|
||||
interface IAckInterface {
|
||||
oneway void onAck();
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// ICallbackInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.IAckInterface;
|
||||
|
||||
interface ICallbackInterface {
|
||||
oneway void onResult(in byte[] data,in boolean isSuccess);
|
||||
oneway void onResult(in byte[] data,in boolean isSuccess, in IAckInterface ack);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// IEventInterface.aidl
|
||||
package com.follow.clash.service;
|
||||
|
||||
import com.follow.clash.service.IAckInterface;
|
||||
|
||||
interface IEventInterface {
|
||||
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess);
|
||||
oneway void onEvent(in String id, in byte[] data,in boolean isSuccess, in IAckInterface ack);
|
||||
}
|
||||
@@ -17,8 +17,10 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.util.UUID
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class RemoteService : Service(),
|
||||
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
|
||||
@@ -75,11 +77,22 @@ class RemoteService : Service(),
|
||||
private val binder = object : IRemoteInterface.Stub() {
|
||||
override fun invokeAction(data: String, callback: ICallbackInterface) {
|
||||
Core.invokeAction(data) {
|
||||
runCatching {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
callback.onResult(chunk, totalSize - 1 == index)
|
||||
launch {
|
||||
runCatching {
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
for ((index, chunk) in chunks.withIndex()) {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
callback.onResult(
|
||||
chunk,
|
||||
index == chunks.lastIndex,
|
||||
object : IAckInterface.Stub() {
|
||||
override fun onAck() {
|
||||
cont.resume(Unit)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +102,7 @@ class RemoteService : Service(),
|
||||
State.notificationParamsFlow.tryEmit(params)
|
||||
}
|
||||
|
||||
|
||||
override fun startService(
|
||||
options: VpnOptions,
|
||||
runtime: Long,
|
||||
@@ -106,12 +120,24 @@ class RemoteService : Service(),
|
||||
GlobalState.log("RemoveEventListener ${eventListener == null}")
|
||||
when (eventListener != null) {
|
||||
true -> Core.callSetEventListener {
|
||||
runCatching {
|
||||
val id = UUID.randomUUID().toString()
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
val totalSize = chunks.size
|
||||
chunks.forEachIndexed { index, chunk ->
|
||||
eventListener.onEvent(id, chunk, totalSize - 1 == index)
|
||||
launch {
|
||||
runCatching {
|
||||
val id = UUID.randomUUID().toString()
|
||||
val chunks = it?.chunkedForAidl() ?: listOf()
|
||||
for ((index, chunk) in chunks.withIndex()) {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
eventListener.onEvent(
|
||||
id,
|
||||
chunk,
|
||||
index == chunks.lastIndex,
|
||||
object : IAckInterface.Stub() {
|
||||
override fun onAck() {
|
||||
cont.resume(Unit)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ class VpnService : SystemVpnService(), IBaseService,
|
||||
allowBypass()
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.systemProxy) {
|
||||
GlobalState.log("Open http proxy")
|
||||
setHttpProxy(
|
||||
ProxyInfo.buildDirectProxy(
|
||||
"127.0.0.1", options.port, options.bypassDomain
|
||||
|
||||
@@ -53,8 +53,8 @@ func handleAction(action *Action, result ActionResult) {
|
||||
result.success(handleShutdown())
|
||||
return
|
||||
case validateConfigMethod:
|
||||
data := []byte(action.Data.(string))
|
||||
result.success(handleValidateConfig(data))
|
||||
path := action.Data.(string)
|
||||
result.success(handleValidateConfig(path))
|
||||
return
|
||||
case updateConfigMethod:
|
||||
data := []byte(action.Data.(string))
|
||||
|
||||
@@ -83,8 +83,9 @@ func handleShutdown() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func handleValidateConfig(bytes []byte) string {
|
||||
_, err := config.UnmarshalRawConfig(bytes)
|
||||
func handleValidateConfig(path string) string {
|
||||
buf, err := readFile(path)
|
||||
_, err = config.UnmarshalRawConfig(buf)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ class AppPath {
|
||||
return join(homeDirPath, 'config.json');
|
||||
}
|
||||
|
||||
Future<String> get validateFilePath async {
|
||||
final homeDirPath = await appPath.homeDirPath;
|
||||
return join(homeDirPath, 'temp', 'validate${utils.id}.yaml');
|
||||
}
|
||||
|
||||
Future<String> get sharedPreferencesPath async {
|
||||
final directory = await dataDir.future;
|
||||
return join(directory.path, 'shared_preferences.json');
|
||||
|
||||
@@ -71,8 +71,20 @@ class CoreController {
|
||||
|
||||
FutureOr<bool> get isInit => _interface.isInit;
|
||||
|
||||
FutureOr<String> validateConfig(String data) {
|
||||
return _interface.validateConfig(data);
|
||||
Future<String> validateConfig(String data) async {
|
||||
final path = await appPath.validateFilePath;
|
||||
await globalState.genValidateFile(path, data);
|
||||
final res = await _interface.validateConfig(path);
|
||||
await File(path).delete();
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<String> validateConfigFormBytes(Uint8List bytes) async {
|
||||
final path = await appPath.validateFilePath;
|
||||
await globalState.genValidateFileFormBytes(path, bytes);
|
||||
final res = await _interface.validateConfig(path);
|
||||
await File(path).delete();
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<String> updateConfig(UpdateParams updateParams) async {
|
||||
|
||||
@@ -17,7 +17,7 @@ mixin CoreInterface {
|
||||
|
||||
Future<bool> forceGc();
|
||||
|
||||
Future<String> validateConfig(String data);
|
||||
Future<String> validateConfig(String path);
|
||||
|
||||
Future<Result> getConfig(String path);
|
||||
|
||||
@@ -125,10 +125,10 @@ abstract class CoreHandlerInterface with CoreInterface {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> validateConfig(String data) async {
|
||||
Future<String> validateConfig(String path) async {
|
||||
return await _invoke<String>(
|
||||
method: ActionMethod.validateConfig,
|
||||
data: data,
|
||||
data: path,
|
||||
) ??
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -174,7 +173,7 @@ extension ProfileExtension on Profile {
|
||||
}
|
||||
|
||||
Future<Profile> saveFile(Uint8List bytes) async {
|
||||
final message = await coreController.validateConfig(utf8.decode(bytes));
|
||||
final message = await coreController.validateConfigFormBytes(bytes);
|
||||
if (message.isNotEmpty) {
|
||||
throw message;
|
||||
}
|
||||
@@ -182,14 +181,4 @@ extension ProfileExtension on Profile {
|
||||
await file.writeAsBytes(bytes);
|
||||
return copyWith(lastUpdateDate: DateTime.now());
|
||||
}
|
||||
|
||||
Future<Profile> saveFileWithString(String value) async {
|
||||
final message = await coreController.validateConfig(value);
|
||||
if (message.isNotEmpty) {
|
||||
throw message;
|
||||
}
|
||||
final file = await getFile();
|
||||
await file.writeAsString(value);
|
||||
return copyWith(lastUpdateDate: DateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ class GlobalState {
|
||||
return VpnOptions(
|
||||
stack: config.patchClashConfig.tun.stack.name,
|
||||
enable: vpnProps.enable,
|
||||
systemProxy: networkProps.systemProxy,
|
||||
systemProxy: vpnProps.systemProxy,
|
||||
port: port,
|
||||
ipv6: vpnProps.ipv6,
|
||||
dnsHijacking: vpnProps.dnsHijacking,
|
||||
@@ -323,6 +323,42 @@ class GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> genValidateFile(String path, String data) async {
|
||||
final res = await Isolate.run<String>(() async {
|
||||
try {
|
||||
final file = File(path);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await file.writeAsString(data);
|
||||
return '';
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
if (res.isNotEmpty) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> genValidateFileFormBytes(String path, Uint8List bytes) async {
|
||||
final res = await Isolate.run<String>(() async {
|
||||
try {
|
||||
final file = File(path);
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
await file.writeAsBytes(bytes);
|
||||
return '';
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
if (res.isNotEmpty) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidState getAndroidState() {
|
||||
return AndroidState(
|
||||
currentProfileName: config.currentProfile?.label ?? '',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.88+2025092301
|
||||
version: 0.8.89+2025092401
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user