diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0b6968d..7d3574c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,14 +1,18 @@ + + + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> @@ -16,6 +20,6 @@ + - \ No newline at end of file diff --git a/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl new file mode 100644 index 0000000..e22570f --- /dev/null +++ b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl @@ -0,0 +1,6 @@ +// IClashService.aidl +package com.github.kr328.clash; + +interface IClashService { + void loadProfile(String path); +} diff --git a/app/src/main/java/com/github/kr328/clash/ClashService.kt b/app/src/main/java/com/github/kr328/clash/ClashService.kt new file mode 100644 index 0000000..b4ee905 --- /dev/null +++ b/app/src/main/java/com/github/kr328/clash/ClashService.kt @@ -0,0 +1,46 @@ +package com.github.kr328.clash + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.os.RemoteException +import android.util.Log +import com.github.kr328.clash.core.Clash +import com.github.kr328.clash.core.ClashException +import java.io.File +import kotlin.concurrent.thread + +class ClashService : Service() { + private val binder = object: IClashService.Stub() { + override fun loadProfile(path: String?) { + if ( path == null || !File(path).exists() ) + throw RemoteException("Invalid path") + + thread { + try { + Clash.loadProfileFromPath(path) + } + catch (e: ClashException) { + Log.w("ClashForAndroid", "Load failure", e) + } + } + } + } + + override fun onCreate() { + super.onCreate() + + Clash.init(filesDir.resolve("clash").absolutePath) + Clash.loadDefault() + } + + override fun onBind(intent: Intent?): IBinder? { + return binder + } + + override fun onDestroy() { + Clash.loadDefault() + + super.onDestroy() + } +} \ No newline at end of file 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 9f61b54..484313b 100644 --- a/app/src/main/java/com/github/kr328/clash/MainActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/MainActivity.kt @@ -1,14 +1,65 @@ package com.github.kr328.clash -import androidx.appcompat.app.AppCompatActivity +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection import android.os.Bundle -import com.github.kr328.clash.core.Clash +import android.os.IBinder +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_main.* +import java.io.FileOutputStream +import java.lang.Exception +import java.lang.RuntimeException +import kotlin.concurrent.thread class MainActivity : AppCompatActivity() { + companion object { + private const val REQUEST_CODE = 233 + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - Clash.loadDefault() + 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 { + FileOutputStream(cacheDir.resolve("config.yaml")).use { + contentResolver.openInputStream(data?.data ?: throw RuntimeException())?.apply { copyTo(it) }?.close() + } + + 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?) { + try { + val clash = IClashService.Stub.asInterface(service) + + clash.loadProfile(cacheDir.resolve("config.yaml").absolutePath) + } + catch (e: Exception) { + Log.e("ClashForAndroid", "Load", e) + } + } + + }, Context.BIND_AUTO_CREATE) + } + } + else { + super.onActivityResult(requestCode, resultCode, data) + } } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ca0c0de..b1f1561 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,13 +6,13 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - - + android:text="Open" /> \ No newline at end of file diff --git a/core/src/main/golang/init.go b/core/src/main/golang/init.go new file mode 100644 index 0000000..4a53df2 --- /dev/null +++ b/core/src/main/golang/init.go @@ -0,0 +1,44 @@ +package main + +/* +#ifndef JNI_COMPILE +#define JNI_COMPILE +#include +#include + +#define CLASH_EXCEPTION_CLASS "com/github/kr328/clash/core/ClashException" + +extern const char *_GoStringPtr(_GoString_ s); + +static void jni_throw_clash_exception(JNIEnv *env, _GoString_ message) { + (*env)->ThrowNew(env, CLASH_EXCEPTION_CLASS, _GoStringPtr(message)); +} + +static char *jni_get_string(JNIEnv *env, jstring string) { + const char *jni_string = (*env)->GetStringUTFChars(env, string, NULL); + char *result = strdup(jni_string); + (*env)->ReleaseStringUTFChars(env, string, jni_string); + return result; +} +#endif //JNI_COMPILE +*/ +import "C" + +import ( + "github.com/Dreamacro/clash/constant" +) + +//export Java_com_github_kr328_clash_core_Clash_init +func Java_com_github_kr328_clash_core_Clash_init(env *C.JNIEnv, class C.jclass, home C.jstring) { + cs := C.jni_get_string(env, home) + if cs == nil { + C.jni_throw_clash_exception(env, "Unable to get path") + return + } + + p := C.GoString(cs) + + C.free(unsafe.Pointer(cs)) + + constant.SetHomeDir(p) +} diff --git a/core/src/main/golang/load.go b/core/src/main/golang/load.go index d5e5fb1..37b58e1 100644 --- a/core/src/main/golang/load.go +++ b/core/src/main/golang/load.go @@ -1,9 +1,34 @@ package main -//#include +/* +#ifndef JNI_COMPILE +#define JNI_COMPILE +#include +#include + +#define CLASH_EXCEPTION_CLASS "com/github/kr328/clash/core/ClashException" + +extern const char *_GoStringPtr(_GoString_ s); + +static void jni_throw_clash_exception(JNIEnv *env, _GoString_ message) { + (*env)->ThrowNew(env, CLASH_EXCEPTION_CLASS, _GoStringPtr(message)); +} + +static char *jni_get_string(JNIEnv *env, jstring string) { + const char *jni_string = (*env)->GetStringUTFChars(env, string, NULL); + char *result = strdup(jni_string); + (*env)->ReleaseStringUTFChars(env, string, jni_string); + return result; +} +#endif //JNI_COMPILE +*/ import "C" -import "github.com/kr328/clash-for-android/log" -import "github.com/kr328/clash-for-android/profile" +import "unsafe" + +import ( + "github.com/kr328/clash-for-android/log" + "github.com/kr328/clash-for-android/profile" +) //export Java_com_github_kr328_clash_core_Clash_loadDefault func Java_com_github_kr328_clash_core_Clash_loadDefault(env *C.JNIEnv, class C.jclass) { @@ -11,3 +36,20 @@ func Java_com_github_kr328_clash_core_Clash_loadDefault(env *C.JNIEnv, class C.j profile.LoadDefault() } + +//export Java_com_github_kr328_clash_core_Clash_loadProfileFromPath +func Java_com_github_kr328_clash_core_Clash_loadProfileFromPath(env *C.JNIEnv, class C.jclass, path C.jstring) { + cs := C.jni_get_string(env, path) + if cs == nil { + C.jni_throw_clash_exception(env, "Unable to get path") + return + } + + p := C.GoString(cs) + + C.free(unsafe.Pointer(cs)) + + if err := profile.LoadFromFile(p); err != nil { + C.jni_throw_clash_exception(env, err.Error()) + } +} diff --git a/core/src/main/golang/log/log.go b/core/src/main/golang/log/log.go index 44b7e02..2384515 100644 --- a/core/src/main/golang/log/log.go +++ b/core/src/main/golang/log/log.go @@ -8,35 +8,28 @@ package log #define TAG "ClashForAndroid" -static void log_info(const char *msg) { - __android_log_print(ANDROID_LOG_INFO, "ClashForAndroid", "%s", msg); +static void log_info(_GoString_ msg) { + __android_log_print(ANDROID_LOG_INFO, "ClashForAndroid", "%s", _GoStringPtr(msg)); } -static void log_error(const char *msg) { - __android_log_print(ANDROID_LOG_ERROR, "ClashForAndroid", "%s", msg); +static void log_error(_GoString_ msg) { + __android_log_print(ANDROID_LOG_ERROR, "ClashForAndroid", "%s", _GoStringPtr(msg)); } -static void log_warn(const char *msg) { - __android_log_print(ANDROID_LOG_WARN, "ClashForAndroid", "%s", msg); +static void log_warn(_GoString_ msg) { + __android_log_print(ANDROID_LOG_WARN, "ClashForAndroid", "%s", _GoStringPtr(msg)); } */ import "C" -import "unsafe" func Info(msg string) { - log := C.CString(msg) - C.log_info(log) - C.free(unsafe.Pointer(log)) + C.log_info(msg) } func Error(msg string) { - log := C.CString(msg) - C.log_error(log) - C.free(unsafe.Pointer(log)) + C.log_error(msg) } func Warn(msg string) { - log := C.CString(msg) - C.log_warn(log) - C.free(unsafe.Pointer(log)) -} \ No newline at end of file + C.log_warn(msg) +} diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.java b/core/src/main/java/com/github/kr328/clash/core/Clash.java index df9b758..08f6b8b 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.java +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.java @@ -1,7 +1,9 @@ package com.github.kr328.clash.core; public final class Clash { + public static native void init(String home); public static native void loadDefault(); + public static native void loadProfileFromPath(String path) throws ClashException; static { System.loadLibrary("clash"); diff --git a/core/src/main/java/com/github/kr328/clash/core/ClashException.java b/core/src/main/java/com/github/kr328/clash/core/ClashException.java new file mode 100644 index 0000000..3dda390 --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/ClashException.java @@ -0,0 +1,7 @@ +package com.github.kr328.clash.core; + +public class ClashException extends Exception { + public ClashException(String message) { + super(message); + } +}