diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3a46911..414c880 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,11 @@ android { arguments "-DANDROID_ABI=arm64-v8a" } } + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } } buildTypes { @@ -58,6 +63,7 @@ dependencies { androidTestImplementation libs.ext.junit androidTestImplementation libs.espresso.core + implementation 'androidx.work:work-runtime:2.9.0' // Retrofit 核心库 implementation 'com.squareup.retrofit2:retrofit:2.9.0' @@ -66,4 +72,4 @@ dependencies { // 如果需要 RxJava 支持(可选) implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e65d736..955202d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,12 +20,6 @@ - - - - - - + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/java/com/example/studyapp/MainActivity.java b/app/src/main/java/com/example/studyapp/MainActivity.java index 32e5a2e..ff3625c 100644 --- a/app/src/main/java/com/example/studyapp/MainActivity.java +++ b/app/src/main/java/com/example/studyapp/MainActivity.java @@ -3,8 +3,6 @@ package com.example.studyapp; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; -import android.content.ComponentName; -import android.content.ServiceConnection; import android.net.Uri; import android.net.VpnService; import android.content.Context; @@ -24,19 +22,22 @@ import android.Manifest; import android.content.pm.PackageManager; import android.os.Environment; -import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.appcompat.app.AppCompatActivity; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; import com.example.studyapp.autoJS.AutoJsUtil; import com.example.studyapp.device.ChangeDeviceInfo; import com.example.studyapp.proxy.CustomVpnService; import com.example.studyapp.utils.ReflectionHelper; +import com.example.studyapp.worker.CheckAccessibilityWorker; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE_STORAGE_PERMISSION = 1; @@ -49,6 +50,7 @@ public class MainActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + System.setProperty("java.library.path", this.getApplicationInfo().nativeLibraryDir); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // 针对 Android 10 或更低版本检查普通存储权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) @@ -110,6 +112,9 @@ public class MainActivity extends AppCompatActivity { Toast.makeText(this, "resetDeviceInfo button not found", Toast.LENGTH_SHORT).show(); } + PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(CheckAccessibilityWorker.class, 15, TimeUnit.MINUTES) + .build(); + WorkManager.getInstance(this).enqueue(workRequest); } private void startProxyVpn(Context context) { @@ -299,4 +304,4 @@ public class MainActivity extends AppCompatActivity { } return false; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/studyapp/config/ConfigLoader.java b/app/src/main/java/com/example/studyapp/config/ConfigLoader.java index 24c46f6..40ec0c2 100644 --- a/app/src/main/java/com/example/studyapp/config/ConfigLoader.java +++ b/app/src/main/java/com/example/studyapp/config/ConfigLoader.java @@ -15,10 +15,10 @@ import java.io.InputStreamReader; public class ConfigLoader { // 从 assets 中读取 JSON 文件并解析 - public static String getTunnelAddress() { + public static String getTunnelAddress(Context context) { String jsonStr; // 获取应用私有目录的文件路径 - File configFile = new File("/data/v2ray/config.json"); + File configFile = new File(context.getCodeCacheDir(),"config.json"); // 检查文件是否存在 if (!configFile.exists()) { diff --git a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java index 80d365d..114f7c1 100644 --- a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java +++ b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java @@ -122,6 +122,12 @@ public class ChangeDeviceInfo { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be null or empty"); + } + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } try { // 获取类对象 @@ -133,20 +139,21 @@ public class ChangeDeviceInfo { putStringMethod.invoke(null, context.getContentResolver(), key, value); Log.d("Debug", "putString executed successfully."); } catch (ClassNotFoundException e) { - Log.e("Reflection Error", "Class not found", e); + Log.w("Reflection Error", "Class not found: android.provider.VCloudSettings$Global. This may not be supported on this device."); } catch (NoSuchMethodException e) { - Log.e("Reflection Error", "Method not found", e); + Log.w("Reflection Error", "Method putString not available. Ensure your target Android version supports it."); } catch (InvocationTargetException e) { - Throwable cause = e.getTargetException(); // 获取异常的根原因 + Throwable cause = e.getTargetException(); if (cause instanceof SecurityException) { - Log.e("Reflection Error", "SecurityException: Permission denied. You need WRITE_SECURE_SETTINGS.", cause); + Log.e("Reflection Error", "Permission denied. Ensure WRITE_SECURE_SETTINGS permission is granted.", cause); } else { Log.e("Reflection Error", "InvocationTargetException during putString invocation", e); } } catch (Exception e) { - Log.e("Reflection Error", "Unexpected error during putString invocation", e); + Log.e("Reflection Error", "Unexpected error during putString invocation: " + e.getMessage()); } } + public static void resetChangedDeviceInfo(String current_pkg_name,Context context) { try { Native.setBootId("00000000000000000000000000000000"); diff --git a/app/src/main/java/com/example/studyapp/device/设备参数.txt b/app/src/main/java/com/example/studyapp/device/bigo设备参数.txt similarity index 100% rename from app/src/main/java/com/example/studyapp/device/设备参数.txt rename to app/src/main/java/com/example/studyapp/device/bigo设备参数.txt diff --git a/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java b/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java index a31059b..405e9bc 100644 --- a/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java +++ b/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java @@ -1,8 +1,6 @@ package com.example.studyapp.proxy; import static com.example.studyapp.utils.IpUtil.isValidIpAddress; -import static com.example.studyapp.utils.V2rayUtil.isV2rayRunning; - import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -12,30 +10,21 @@ import android.net.VpnService; import android.os.Build; import android.os.ParcelFileDescriptor; import android.util.Log; - import androidx.core.app.NotificationCompat; - import com.example.studyapp.R; import com.example.studyapp.config.ConfigLoader; import com.example.studyapp.utils.SingboxUtil; -import com.example.studyapp.utils.V2rayUtil; - import java.io.BufferedReader; -import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; public class CustomVpnService extends VpnService { - - private static final String TUN_ADDRESS = ConfigLoader.getTunnelAddress(); // TUN 的 IP 地址 private static final int PREFIX_LENGTH = 28; // 子网掩码 private Thread vpnTrafficThread; // 保存线程引用 @@ -53,7 +42,7 @@ public class CustomVpnService extends VpnService { // 开始前台服务 startForeground(NOTIFICATION_ID, createNotification()); try { - // 检查 V2ray 是否已启动,避免重复进程 + // 检查 Singbox 是否已启动,避免重复进程 SingboxUtil.startSingBox(getApplicationContext()); // 启动 VPN 流量服务 @@ -72,10 +61,10 @@ public class CustomVpnService extends VpnService { // 不再需要手动验证 TUN_ADDRESS 和 PREFIX_LENGTH // 直接使用系统权限建立虚拟网卡,用于 TUN 接口和流量捕获 - builder.addAddress(TUN_ADDRESS, PREFIX_LENGTH); // 保证 TUN 接口 IP 地址仍与 v2ray 配置文件保持一致 + builder.addAddress(ConfigLoader.getTunnelAddress(this), PREFIX_LENGTH); // 保证 TUN 接口 IP 地址仍与 singbox 配置文件保持一致 builder.addRoute("0.0.0.0", 0); // 捕获所有 IPv4 流量 - // DNS 部分,如果有需要,也可以简化或直接保留 v2ray 配置提供的 + // DNS 部分,如果有需要,也可以简化或直接保留 singbox 配置提供的 List dnsServers = getSystemDnsServers(); if (dnsServers.isEmpty()) { // 如果未能从系统中获取到 DNS 地址,添加备用默认值 @@ -100,7 +89,7 @@ public class CustomVpnService extends VpnService { throw new IllegalStateException("VPN Interface establishment failed"); } - // 核心:启动流量转发服务,此后转发逻辑由 v2ray 接管 + // 核心:启动流量转发服务,此后转发逻辑由 singbox 接管 new Thread(() -> handleVpnTraffic(vpnInterface)).start(); } catch (Exception e) { @@ -175,7 +164,7 @@ public class CustomVpnService extends VpnService { Log.d("CustomVpnService", "Packet Info: TCP=" + isTcpPacket + ", DNS=" + isDnsPacket + ", Length=" + length); if (isTcpPacket || isDnsPacket) { - Log.i("CustomVpnService", "Forwarding to V2Ray. Packet Length: " + length); + Log.i("CustomVpnService", "Forwarding to Singbox. Packet Length: " + length); return true; } } catch (ArrayIndexOutOfBoundsException e) { @@ -215,7 +204,7 @@ public class CustomVpnService extends VpnService { vpnInterface = null; // 避免资源泄露 } - // 停止 V2Ray 服务 + // 停止 Singbox 服务 SingboxUtil.stopSingBox(); Log.i("CustomVpnService", "VPN 服务已停止"); return true; @@ -248,7 +237,7 @@ public class CustomVpnService extends VpnService { vpnInterface = null; // 避免资源泄露 } - // 停止 V2Ray 服务 + // 停止 Singbox 服务 SingboxUtil.stopSingBox(); Log.i("CustomVpnService", "VPN 服务已销毁"); } @@ -320,18 +309,27 @@ public class CustomVpnService extends VpnService { private Notification createNotification() { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String channelId = "vpn_service_channel"; NotificationChannel channel = new NotificationChannel( - "vpn_service", + channelId, "VPN Service", - NotificationManager.IMPORTANCE_DEFAULT + NotificationManager.IMPORTANCE_LOW // 设置为低优先级,避免打扰用户 ); - notificationManager.createNotificationChannel(channel); + channel.setDescription("通知用户 VPN 服务正在运行中"); + + // 注册通道 + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } } - return new NotificationCompat.Builder(this, "vpn_service") + + return new NotificationCompat.Builder(this, "vpn_service_channel") .setContentTitle("VPN 服务") - .setContentText("VPN 正在运行...") - .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentText("VPN 正在运行中...") + .setSmallIcon(R.drawable.ic_launcher_foreground) // 需要替换成实际图标资源 + .setPriority(NotificationCompat.PRIORITY_LOW) // 设置为低优先级 .build(); } diff --git a/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java b/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java new file mode 100644 index 0000000..40b975c --- /dev/null +++ b/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java @@ -0,0 +1,62 @@ +package com.example.studyapp.service; + +import android.accessibilityservice.AccessibilityService; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; + +import android.view.accessibility.AccessibilityEvent; +import androidx.core.app.NotificationCompat; +import com.example.studyapp.MainActivity; +import com.example.studyapp.R; + +public class MyAccessibilityService extends AccessibilityService { + + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + + } + + @Override + public void onInterrupt() { + + } + + @Override + protected void onServiceConnected() { + super.onServiceConnected(); + startForegroundService(); + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String channelId = "2"; + String channelName = "Foreground Service"; + int importance = NotificationManager.IMPORTANCE_LOW; + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + NotificationManager notificationManager = getSystemService(NotificationManager.class); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + } + + private void startForegroundService() { + createNotificationChannel(); + Intent notificationIntent = new Intent(this, MainActivity.class); + int pendingIntentFlags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, pendingIntentFlags); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "2") + .setContentTitle("无障碍服务运行中") + .setContentText("此服务正在持续运行") + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentIntent(pendingIntent); + android.app.Notification notification = notificationBuilder.build(); + startForeground(1, notification); + } +} diff --git a/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java b/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java index 965ca3b..705a32d 100644 --- a/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java +++ b/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java @@ -2,57 +2,70 @@ package com.example.studyapp.utils; import android.content.Context; import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.util.Base64; import android.util.Log; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; public class SingboxUtil { + private static boolean isLibraryLoaded = false; + + static { + try { + System.loadLibrary("singbox"); + isLibraryLoaded = true; + } catch (UnsatisfiedLinkError e) { + Log.e("SingBox", "Failed to load singbox library.", e); + } + } + + public static native int StartVpn(); + + public static native int StopVpn(); + private static File singBoxBinary, singBoxConfig; public static void startSingBox(Context context) { - if (isSingBoxRunning(context)) { - Log.i("SingBox", "singbox is already running. Skipping start."); - return; - } + synchronized (SingboxUtil.class) { // 确保线程安全 + if (!isLibraryLoaded) { + Log.e("SingBox", "Native library not loaded. Cannot perform StopVpn."); + return; + } - if (!ensureSingBoxFilesExist(context)) { - Log.e("SingBox", "Singbox files are missing."); - return; - } - - try { - ProcessBuilder builder = new ProcessBuilder( - singBoxBinary.getAbsolutePath(), "run", "-c", singBoxConfig.getAbsolutePath() - ).redirectErrorStream(true); - - Process process = builder.start(); - Log.i("SingBox", "SingBox service started."); - - new Thread(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - Log.d("SingBox", line); - } - } catch (IOException e) { - Log.e("SingBox", "Error reading process output", e); + if (!ensureSingBoxFilesExist(context)) { + Log.e("SingBox", "Singbox files are missing."); + return; + } + int result = StartVpn(); + if (result == 0) { + Log.i("SingBox", "SingBox service started successfully using StartVpn."); + } else { + Log.e("SingBox", "Failed to start SingBox service. Return code: " + result); } - }).start(); + } + } - int exitCode = process.waitFor(); // 等待进程完成 - Log.i("SingBox", "SingBox process exited with code: " + exitCode); - - } catch (IOException e) { - Log.e("SingBox", "Failed to start SingBox process", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - Log.e("SingBox", "Process was interrupted", e); + // 简单的 JSON 验证函数示例 + private static boolean isValidJson(String json) { + try { + new org.json.JSONObject(json); // or new org.json.JSONArray(json); + return true; + } catch (org.json.JSONException ex) { + return false; } } @@ -91,7 +104,7 @@ public class SingboxUtil { } // 检查并复制配置文件 - singBoxConfig = new File("/data/singbox/config.json"); + singBoxConfig = new File(context.getCodeCacheDir(), "config.json"); if (!singBoxConfig.exists()) { try (InputStream configInputStream = context.getAssets().open("singbox/" + abi + "/config.json"); @@ -119,51 +132,35 @@ public class SingboxUtil { return false; } - // 判断是否有正在运行的 singbox - public static boolean isSingBoxRunning(Context context) { - android.app.ActivityManager activityManager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - java.util.List runningProcesses = activityManager.getRunningAppProcesses(); - if (runningProcesses != null) { - for (android.app.ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { - if (processInfo.processName.contains("sing-box")) { - return true; - } - } - } - return false; - } - public static void stopSingBox() { - Process process = null; - try { - // 检查是否支持 `pkill` 并停止 sing-box 进程 - ProcessBuilder builder = new ProcessBuilder("pkill", "-f", "sing-box"); - process = builder.start(); - - // 等待进程执行完成 - int exitCode = process.waitFor(); - if (exitCode == 0) { - Log.i("SingBox", "singbox process stopped successfully."); - } else { - Log.e("SingBox", "Failed to stop singbox process. Exit code: " + exitCode); + synchronized (SingboxUtil.class) { // 防止并发冲突 + if (!isLibraryLoaded) { + Log.e("SingBox", "Native library not loaded. Cannot perform StopVpn."); + return; } - } catch (IOException e) { - Log.e("SingBox", "IOException occurred while trying to stop singbox", e); - } catch (InterruptedException e) { - Log.e("SingBox", "The stopSingBox process was interrupted", e); - Thread.currentThread().interrupt(); // 恢复中断状态 - } catch (Exception e) { - Log.e("SingBox", "Unexpected error occurred", e); - } finally { - // 确保关闭流以避免资源泄露 - if (process != null) { - try { - process.getInputStream().close(); - process.getErrorStream().close(); - } catch (IOException e) { - Log.e("SingBox", "Failed to close process streams", e); + + try { + Log.d("SingBox", "Invoking StopVpn method on thread: " + Thread.currentThread().getName()); + int result = StopVpn(); + switch (result) { + case 0: + Log.i("SingBox", "SingBox service stopped successfully using StopVpn."); + break; + case -1: + Log.e("SingBox", "Failed to stop SingBox: Instance is not initialized or already stopped."); + break; + case -2: + Log.e("SingBox", "Failed to stop SingBox: Unexpected error occurred during the shutdown."); + break; + default: + Log.e("SingBox", "Unexpected StopVpn error code: " + result); + break; } + } catch (UnsatisfiedLinkError e) { + Log.e("SingBox", "Failed to load native library for stopping VPN.", e); + } catch (Exception e) { + Log.e("SingBox", "Unexpected error occurred while stopping SingBox using StopVpn.", e); } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/studyapp/worker/CheckAccessibilityWorker.java b/app/src/main/java/com/example/studyapp/worker/CheckAccessibilityWorker.java new file mode 100644 index 0000000..6face92 --- /dev/null +++ b/app/src/main/java/com/example/studyapp/worker/CheckAccessibilityWorker.java @@ -0,0 +1,70 @@ +package com.example.studyapp.worker; + +import android.accessibilityservice.AccessibilityService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.provider.Settings; +import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.work.CoroutineWorker; +import androidx.work.WorkerParameters; +import com.example.studyapp.service.MyAccessibilityService; +import kotlin.coroutines.Continuation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CheckAccessibilityWorker extends CoroutineWorker { + + public CheckAccessibilityWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + public boolean isAccessibilityServiceEnabled(Context context, Class service) { + String enabledServices = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ); + + // 检查是否获取到了内容并进行处理 + if (TextUtils.isEmpty(enabledServices)) { + return false; + } + + // 利用 split 高效解析字符串 + String[] components = enabledServices.split(":"); + String expectedComponentName = new ComponentName(context.getPackageName(), service.getCanonicalName()).flattenToString(); + + // 使用 foreach 检查是否匹配 + for (String componentName : components) { + if (expectedComponentName.equalsIgnoreCase(componentName)) { + return true; + } + } + return false; + } + + + @Override + public @Nullable Object doWork(@NotNull Continuation continuation) { + if (!isAccessibilityServiceEnabled(getApplicationContext(), MyAccessibilityService.class)) { + // 判断是否已经提示过用户引导开启 + SharedPreferences sharedPreferences = getApplicationContext() + .getSharedPreferences("my_app_prefs", Context.MODE_PRIVATE); + boolean hasPrompted = sharedPreferences.getBoolean("accessibility_prompted", false); + + if (!hasPrompted) { + // 引导用户打开辅助功能服务 + Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(intent); + + // 更新状态 + sharedPreferences.edit().putBoolean("accessibility_prompted", true).apply(); + } + return Result.retry(); + } + return Result.success(); + } +} diff --git a/app/src/main/jniLibs/arm64-v8a/libnative.so b/app/src/main/jniLibs/arm64-v8a/libnative.so index 7b35fdd..6bebff1 100644 Binary files a/app/src/main/jniLibs/arm64-v8a/libnative.so and b/app/src/main/jniLibs/arm64-v8a/libnative.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libsingbox.h b/app/src/main/jniLibs/arm64-v8a/libsingbox.h new file mode 100644 index 0000000..9d66108 --- /dev/null +++ b/app/src/main/jniLibs/arm64-v8a/libsingbox.h @@ -0,0 +1,101 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + +#line 3 "singbox.go" + +#include // 声明 strlen + +#line 1 "cgo-generated-wrapper" + + +/* End of preamble from import "C" comments. */ + + +/* Start of boilerplate cgo prologue. */ +#line 1 "cgo-gcc-export-header-prolog" + +#ifndef GO_CGO_PROLOGUE_H +#define GO_CGO_PROLOGUE_H + +typedef signed char GoInt8; +typedef unsigned char GoUint8; +typedef short GoInt16; +typedef unsigned short GoUint16; +typedef int GoInt32; +typedef unsigned int GoUint32; +typedef long long GoInt64; +typedef unsigned long long GoUint64; +typedef GoInt64 GoInt; +typedef GoUint64 GoUint; +typedef size_t GoUintptr; +typedef float GoFloat32; +typedef double GoFloat64; +#ifdef _MSC_VER +#include +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +typedef float _Complex GoComplex64; +typedef double _Complex GoComplex128; +#endif + +/* + static assertion to make sure the file is being used on architecture + at least with matching size of GoInt. +*/ +typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef _GoString_ GoString; +#endif +typedef void *GoMap; +typedef void *GoChan; +typedef struct { void *t; void *v; } GoInterface; +typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; + +#endif + +/* End of boilerplate cgo prologue. */ + +#ifdef __cplusplus +extern "C" { +#endif + + +// startVPN 初始化并启动带有给定配置的 VPN 实例。 +// 接受 JSON 配置字符串作为输入。 +// 返回 0(如果成功),返回 -1 表示失败。 +// +extern int Java_com_example_studyapp_utils_SingboxUtil_StartVpn(); + +// stopVPN 停止当前的 VPN 实例,确保线程安全,处理错误和故障。 +// 返回 0(成功),返回 -1 表示实例未初始化或已关闭,返回 -2 表示关闭时发生错误。 +// +extern int Java_com_example_studyapp_utils_SingboxUtil_StopVpn(); + +// isrunning 返回当前实例是否运行中。 +// 它是线程安全的。 +// +extern GoUint8 Java_com_example_studyapp_utils_SingboxUtil_IsRunning(); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/jniLibs/arm64-v8a/libsingbox.so b/app/src/main/jniLibs/arm64-v8a/libsingbox.so new file mode 100644 index 0000000..23edc8b Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libsingbox.so differ diff --git a/app/src/main/res/xml/AccessibilityServiceConfig.xml b/app/src/main/res/xml/AccessibilityServiceConfig.xml new file mode 100644 index 0000000..4fcb63e --- /dev/null +++ b/app/src/main/res/xml/AccessibilityServiceConfig.xml @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 565f8c2..ac77d27 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + dependencies { + // 替换以下版本 + classpath 'com.android.tools.build:gradle:8.10.1' + } +} plugins { alias(libs.plugins.android.application) apply false -} \ No newline at end of file +} diff --git a/err.log b/err.log index 1592308..b7ab9aa 100644 --- a/err.log +++ b/err.log @@ -1,2 +1,209 @@ -2025-05-30 15:22:09.569 18506-18506 V2rayUtil com.example.studyapp E Failed to create directory: /data/v2ray -2025-05-30 15:22:09.569 18506-18506 V2Ray com.example.studyapp E V2Ray files are missing, cannot start. \ No newline at end of file +2025-06-04 19:05:48.535 19792-19792 SingBox com.example.studyapp I Copied singbox config.json to: /data/user/0/com.example.studyapp/code_cache/config.json +2025-06-04 19:05:48.536 19792-19792 SingBox com.example.studyapp D Config passed to StartVpn: { + "log": { "level": "trace" }, + "dns": { + "final": "cloudflare", + "independent_cache": true, + "servers": [ + { + "tag": "cloudflare", + "address": "tls://1.1.1.1" + }, + { + "tag": "local", + "address": "tls://1.1.1.1", + "detour": "direct" + }, + { + "tag": "remote", + "address": "fakeip" + } + ], + "rules": [ + { + "server": "local", + "outbound": "any" + }, + { + "server": "remote", + "query_type": ["A", "AAAA"] + } + ], + "fakeip": { + "enabled": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + }, + "inbounds": [ + { + "type": "tun", + "tag": "tun-in", + "address": ["172.19.0.1/28"], + "auto_route": true, + "sniff": true, + "strict_route": false, + "domain_strategy": "ipv4_only" + } + ], + "outbounds": [ + { + "type": "socks", + "tag": "socks-out", + "version": "5", + "network": "tcp", + "udp_over_tcp": { + "enabled": true + }, + "username": "cut_team_protoc_vast-zone-custom-region-us", + "password": "Leoliu811001", + "server": "105bd58a50330382.na.ipidea.online", + "server_port": 2333 + }, + { + "type": "dns", + "tag": "dns-out" + }, + { + "type": "direct", + "tag": "direct" + }, + { + "type": "block", + "tag": "block" + } + ], + "route": { + "final": "socks-out", + "auto_detect_interface": true, + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "protocol": ["stun", "quic"], + "outbound": "block" + }, + { + "ip_is_private": true, + "outbound": "direct" + }, + { + "ip_cidr": "8.217.74.194/32", + "outbound": "direct" + }, + { + "domain": "cpm-api.resi-prod.resi-oversea.com", + "domain_suffix": "resi-oversea.com", + "outbound": "direct" + } + ] + } + } +2025-06-04 19:05:48.538 19792-19792 SingBox com.example.studyapp D UTF-8 Bytes (before JNI): [123, 10, 32, 32, 34, 108, 111, 103, 34, 58, 32, 123, 32, 34, 108, 101, 118, 101, 108, 34, 58, 32, 34, 116, 114, 97, 99, 101, 34, 32, 125, 44, 10, 32, 32, 34, 100, 110, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 102, 105, 110, 97, 108, 34, 58, 32, 34, 99, 108, 111, 117, 100, 102, 108, 97, 114, 101, 34, 44, 10, 32, 32, 32, 32, 34, 105, 110, 100, 101, 112, 101, 110, 100, 101, 110, 116, 95, 99, 97, 99, 104, 101, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 34, 115, 101, 114, 118, 101, 114, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 99, 108, 111, 117, 100, 102, 108, 97, 114, 101, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 58, 32, 34, 116, 108, 115, 58, 47, 47, 49, 46, 49, 46, 49, 46, 49, 34, 10, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 108, 111, 99, 97, 108, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 58, 32, 34, 116, 108, 115, 58, 47, 47, 49, 46, 49, 46, 49, 46, 49, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 101, 116, 111, 117, 114, 34, 58, 32, 34, 100, 105, 114, 101, 99, 116, 34, 10, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 114, 101, 109, 111, 116, 101, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 58, 32, 34, 102, 97, 107, 101, 105, 112, 34, 10, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 114, 117, 108, 101, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 101, 114, 118, 101, 114, 34, 58, 32, 34, 108, 111, 99, 97, 108, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 111, 117, 116, 98, 111, 117, 110, 100, 34, 58, 32, 34, 97, 110, 121, 34, 10, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 101, 114, 118, 101, 114, 34, 58, 32, 34, 114, 101, 109, 111, 116, 101, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 113, 117, 101, 114, 121, 95, 116, 121, 112, 101, 34, 58, 32, 91, 34, 65, 34, 44, 32, 34, 65, 65, 65, 65, 34, 93, 10, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 102, 97, 107, 101, 105, 112, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 32, 34, 101, 110, 97, 98, 108, 101, 100, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 34, 105, 110, 101, 116, 52, 95, 114, 97, 110, 103, 101, 34, 58, 32, 34, 49, 57, 56, 46, 49, 56, 46, 48, 46, 48, 47, 49, 53, 34, 44, 10, 32, 32, 32, 32, 32, 32, 34, 105, 110, 101, 116, 54, 95, 114, 97, 110, 103, 101, 34, 58, 32, 34, 102, 99, 48, 48, 58, 58, 47, 49, 56, 34, 10, 32, 32, 32, 32, 125, 10, 32, 32, 125, 44, 10, 32, 32, 34, 105, 110, 98, 111, 117, 110, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 34, 116, 121, 112, 101, 34, 58, 32, 34, 116, 117, 110, 34, 44, 10, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 116, 117, 110, 45, 105, 110, 34, 44, 10, 32, 32, 32, 32, 32, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 58, 32, 91, 34, 49, 55, 50, 46, 49, 57, 46, 48, 46, 49, 47, 50, 56, 34, 93, 44, 10, 32, 32, 32, 32, 32, 32, 34, 97, 117, 116, 111, 95, 114, 111, 117, 116, 101, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 34, 115, 110, 105, 102, 102, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 34, 115, 116, 114, 105, 99, 116, 95, 114, 111, 117, 116, 101, 34, 58, 32, 102, 97, 108, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 34, 100, 111, 109, 97, 105, 110, 95, 115, 116, 114, 97, 116, 101, 103, 121, 34, 58, 32, 34, 105, 112, 118, 52, 95, 111, 110, 108, 121, 34, 10, 32, 32, 32, 32, 125, 10, 32, 32, 93, 44, 10, 32, 32, 34, 111, 117, 116, 98, 111, 117, 110, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 34, 116, 121, 112, 101 +2025-06-04 19:05:48.538 19792-19792 SingBox com.example.studyapp D Config Java HashCode (for cross-check): -457188023 +2025-06-04 19:05:48.541 19792-19792 SingBox com.example.studyapp E Failed to start SingBox service. Return code: -1, Config: { + "log": { "level": "trace" }, + "dns": { + "final": "cloudflare", + "independent_cache": true, + "servers": [ + { + "tag": "cloudflare", + "address": "tls://1.1.1.1" + }, + { + "tag": "local", + "address": "tls://1.1.1.1", + "detour": "direct" + }, + { + "tag": "remote", + "address": "fakeip" + } + ], + "rules": [ + { + "server": "local", + "outbound": "any" + }, + { + "server": "remote", + "query_type": ["A", "AAAA"] + } + ], + "fakeip": { + "enabled": true, + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + }, + "inbounds": [ + { + "type": "tun", + "tag": "tun-in", + "address": ["172.19.0.1/28"], + "auto_route": true, + "sniff": true, + "strict_route": false, + "domain_strategy": "ipv4_only" + } + ], + "outbounds": [ + { + "type": "socks", + "tag": "socks-out", + "version": "5", + "network": "tcp", + "udp_over_tcp": { + "enabled": true + }, + "username": "cut_team_protoc_vast-zone-custom-region-us", + "password": "Leoliu811001", + "server": "105bd58a50330382.na.ipidea.online", + "server_port": 2333 + }, + { + "type": "dns", + "tag": "dns-out" + }, + { + "type": "direct", + "tag": "direct" + }, + { + "type": "block", + "tag": "block" + } + ], + "route": { + "final": "socks-out", + "auto_detect_interface": true, + "rules": [ + { + "protocol": "dns", + "outbound": "dns-out" + }, + { + "protocol": ["stun", "quic"], + "outbound": "block" + }, + { + "ip_is_private": true, + "outbound": "direct" + }, + { + "ip_cidr": "8.217.74.194/32", + "outbound": "direct" + }, + { + "domain": "cpm-api.resi-prod.resi-oversea.com", + "domain_suffix": "resi-oversea.com", + "outbound": "direct" + } + ] + } + } +2025-06-04 19:05:48.541 19792-20002 GoLog com.example.studyapp E [INFO]: Received Base64 config: ?? ?t +2025-06-04 19:05:48.542 19792-20002 GoLog com.example.studyapp E [ERROR]: Failed to decode Base64 configuration: illegal base64 data at input byte 0. Possible reasons: Input contains invalid Base64 characters or is improperly formatted. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index acf0863..d238fa8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,4 +19,3 @@ constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayo [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -