Integrate WorkManager for periodic accessibility service checks
Added `CheckAccessibilityWorker` with WorkManager to periodically validate and prompt accessibility service activation. Registered new `MyAccessibilityService` in manifest and set configurations. Introduced native `Singbox` methods for VPN operations and added compatibility fixes, including JNI library path initialization. Updated dependency `androidx.work:work-runtime:2.9.0`.
This commit is contained in:
parent
be91f9ea3b
commit
c20f4cb515
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -21,6 +21,11 @@ android {
|
||||||
arguments "-DANDROID_ABI=arm64-v8a"
|
arguments "-DANDROID_ABI=arm64-v8a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -58,6 +63,7 @@ dependencies {
|
||||||
androidTestImplementation libs.ext.junit
|
androidTestImplementation libs.ext.junit
|
||||||
androidTestImplementation libs.espresso.core
|
androidTestImplementation libs.espresso.core
|
||||||
|
|
||||||
|
implementation 'androidx.work:work-runtime:2.9.0'
|
||||||
// Retrofit 核心库
|
// Retrofit 核心库
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,6 @@
|
||||||
<uses-permission android:name="android.permission.CREATE_USERS" />
|
<uses-permission android:name="android.permission.CREATE_USERS" />
|
||||||
<uses-permission android:name="android.permission.QUERY_USERS" />
|
<uses-permission android:name="android.permission.QUERY_USERS" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
@ -50,13 +44,26 @@
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
android:name=".proxy.CustomVpnService"
|
android:name=".proxy.CustomVpnService"
|
||||||
|
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService" />
|
<action android:name="android.net.VpnService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".service.MyAccessibilityService"
|
||||||
|
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="accessibility" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.accessibilityservice"
|
||||||
|
android:resource="@xml/accessibility_service_config" />
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -3,8 +3,6 @@ package com.example.studyapp;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.VpnService;
|
import android.net.VpnService;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -24,19 +22,22 @@ import android.Manifest;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import androidx.work.PeriodicWorkRequest;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
import com.example.studyapp.autoJS.AutoJsUtil;
|
import com.example.studyapp.autoJS.AutoJsUtil;
|
||||||
import com.example.studyapp.device.ChangeDeviceInfo;
|
import com.example.studyapp.device.ChangeDeviceInfo;
|
||||||
import com.example.studyapp.proxy.CustomVpnService;
|
import com.example.studyapp.proxy.CustomVpnService;
|
||||||
import com.example.studyapp.utils.ReflectionHelper;
|
import com.example.studyapp.utils.ReflectionHelper;
|
||||||
|
|
||||||
|
import com.example.studyapp.worker.CheckAccessibilityWorker;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
|
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
|
||||||
|
@ -49,6 +50,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
System.setProperty("java.library.path", this.getApplicationInfo().nativeLibraryDir);
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
// 针对 Android 10 或更低版本检查普通存储权限
|
// 针对 Android 10 或更低版本检查普通存储权限
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
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();
|
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) {
|
private void startProxyVpn(Context context) {
|
||||||
|
|
|
@ -15,10 +15,10 @@ import java.io.InputStreamReader;
|
||||||
public class ConfigLoader {
|
public class ConfigLoader {
|
||||||
|
|
||||||
// 从 assets 中读取 JSON 文件并解析
|
// 从 assets 中读取 JSON 文件并解析
|
||||||
public static String getTunnelAddress() {
|
public static String getTunnelAddress(Context context) {
|
||||||
String jsonStr;
|
String jsonStr;
|
||||||
// 获取应用私有目录的文件路径
|
// 获取应用私有目录的文件路径
|
||||||
File configFile = new File("/data/v2ray/config.json");
|
File configFile = new File(context.getCodeCacheDir(),"config.json");
|
||||||
|
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
if (!configFile.exists()) {
|
if (!configFile.exists()) {
|
||||||
|
|
|
@ -122,6 +122,12 @@ public class ChangeDeviceInfo {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
throw new IllegalArgumentException("Context cannot be 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 {
|
try {
|
||||||
// 获取类对象
|
// 获取类对象
|
||||||
|
@ -133,20 +139,21 @@ public class ChangeDeviceInfo {
|
||||||
putStringMethod.invoke(null, context.getContentResolver(), key, value);
|
putStringMethod.invoke(null, context.getContentResolver(), key, value);
|
||||||
Log.d("Debug", "putString executed successfully.");
|
Log.d("Debug", "putString executed successfully.");
|
||||||
} catch (ClassNotFoundException e) {
|
} 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) {
|
} 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) {
|
} catch (InvocationTargetException e) {
|
||||||
Throwable cause = e.getTargetException(); // 获取异常的根原因
|
Throwable cause = e.getTargetException();
|
||||||
if (cause instanceof SecurityException) {
|
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 {
|
} else {
|
||||||
Log.e("Reflection Error", "InvocationTargetException during putString invocation", e);
|
Log.e("Reflection Error", "InvocationTargetException during putString invocation", e);
|
||||||
}
|
}
|
||||||
} catch (Exception 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) {
|
public static void resetChangedDeviceInfo(String current_pkg_name,Context context) {
|
||||||
try {
|
try {
|
||||||
Native.setBootId("00000000000000000000000000000000");
|
Native.setBootId("00000000000000000000000000000000");
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package com.example.studyapp.proxy;
|
package com.example.studyapp.proxy;
|
||||||
|
|
||||||
import static com.example.studyapp.utils.IpUtil.isValidIpAddress;
|
import static com.example.studyapp.utils.IpUtil.isValidIpAddress;
|
||||||
import static com.example.studyapp.utils.V2rayUtil.isV2rayRunning;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
@ -12,30 +10,21 @@ import android.net.VpnService;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import com.example.studyapp.R;
|
import com.example.studyapp.R;
|
||||||
import com.example.studyapp.config.ConfigLoader;
|
import com.example.studyapp.config.ConfigLoader;
|
||||||
import com.example.studyapp.utils.SingboxUtil;
|
import com.example.studyapp.utils.SingboxUtil;
|
||||||
import com.example.studyapp.utils.V2rayUtil;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
public class CustomVpnService extends VpnService {
|
public class CustomVpnService extends VpnService {
|
||||||
|
|
||||||
|
|
||||||
private static final String TUN_ADDRESS = ConfigLoader.getTunnelAddress(); // TUN 的 IP 地址
|
|
||||||
private static final int PREFIX_LENGTH = 28; // 子网掩码
|
private static final int PREFIX_LENGTH = 28; // 子网掩码
|
||||||
|
|
||||||
private Thread vpnTrafficThread; // 保存线程引用
|
private Thread vpnTrafficThread; // 保存线程引用
|
||||||
|
@ -53,7 +42,7 @@ public class CustomVpnService extends VpnService {
|
||||||
// 开始前台服务
|
// 开始前台服务
|
||||||
startForeground(NOTIFICATION_ID, createNotification());
|
startForeground(NOTIFICATION_ID, createNotification());
|
||||||
try {
|
try {
|
||||||
// 检查 V2ray 是否已启动,避免重复进程
|
// 检查 Singbox 是否已启动,避免重复进程
|
||||||
|
|
||||||
SingboxUtil.startSingBox(getApplicationContext());
|
SingboxUtil.startSingBox(getApplicationContext());
|
||||||
// 启动 VPN 流量服务
|
// 启动 VPN 流量服务
|
||||||
|
@ -72,10 +61,10 @@ public class CustomVpnService extends VpnService {
|
||||||
|
|
||||||
// 不再需要手动验证 TUN_ADDRESS 和 PREFIX_LENGTH
|
// 不再需要手动验证 TUN_ADDRESS 和 PREFIX_LENGTH
|
||||||
// 直接使用系统权限建立虚拟网卡,用于 TUN 接口和流量捕获
|
// 直接使用系统权限建立虚拟网卡,用于 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 流量
|
builder.addRoute("0.0.0.0", 0); // 捕获所有 IPv4 流量
|
||||||
|
|
||||||
// DNS 部分,如果有需要,也可以简化或直接保留 v2ray 配置提供的
|
// DNS 部分,如果有需要,也可以简化或直接保留 singbox 配置提供的
|
||||||
List<String> dnsServers = getSystemDnsServers();
|
List<String> dnsServers = getSystemDnsServers();
|
||||||
if (dnsServers.isEmpty()) {
|
if (dnsServers.isEmpty()) {
|
||||||
// 如果未能从系统中获取到 DNS 地址,添加备用默认值
|
// 如果未能从系统中获取到 DNS 地址,添加备用默认值
|
||||||
|
@ -100,7 +89,7 @@ public class CustomVpnService extends VpnService {
|
||||||
throw new IllegalStateException("VPN Interface establishment failed");
|
throw new IllegalStateException("VPN Interface establishment failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心:启动流量转发服务,此后转发逻辑由 v2ray 接管
|
// 核心:启动流量转发服务,此后转发逻辑由 singbox 接管
|
||||||
new Thread(() -> handleVpnTraffic(vpnInterface)).start();
|
new Thread(() -> handleVpnTraffic(vpnInterface)).start();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -175,7 +164,7 @@ public class CustomVpnService extends VpnService {
|
||||||
Log.d("CustomVpnService", "Packet Info: TCP=" + isTcpPacket + ", DNS=" + isDnsPacket + ", Length=" + length);
|
Log.d("CustomVpnService", "Packet Info: TCP=" + isTcpPacket + ", DNS=" + isDnsPacket + ", Length=" + length);
|
||||||
|
|
||||||
if (isTcpPacket || isDnsPacket) {
|
if (isTcpPacket || isDnsPacket) {
|
||||||
Log.i("CustomVpnService", "Forwarding to V2Ray. Packet Length: " + length);
|
Log.i("CustomVpnService", "Forwarding to Singbox. Packet Length: " + length);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
@ -215,7 +204,7 @@ public class CustomVpnService extends VpnService {
|
||||||
vpnInterface = null; // 避免资源泄露
|
vpnInterface = null; // 避免资源泄露
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止 V2Ray 服务
|
// 停止 Singbox 服务
|
||||||
SingboxUtil.stopSingBox();
|
SingboxUtil.stopSingBox();
|
||||||
Log.i("CustomVpnService", "VPN 服务已停止");
|
Log.i("CustomVpnService", "VPN 服务已停止");
|
||||||
return true;
|
return true;
|
||||||
|
@ -248,7 +237,7 @@ public class CustomVpnService extends VpnService {
|
||||||
vpnInterface = null; // 避免资源泄露
|
vpnInterface = null; // 避免资源泄露
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止 V2Ray 服务
|
// 停止 Singbox 服务
|
||||||
SingboxUtil.stopSingBox();
|
SingboxUtil.stopSingBox();
|
||||||
Log.i("CustomVpnService", "VPN 服务已销毁");
|
Log.i("CustomVpnService", "VPN 服务已销毁");
|
||||||
}
|
}
|
||||||
|
@ -320,18 +309,27 @@ public class CustomVpnService extends VpnService {
|
||||||
private Notification createNotification() {
|
private Notification createNotification() {
|
||||||
NotificationManager notificationManager =
|
NotificationManager notificationManager =
|
||||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
String channelId = "vpn_service_channel";
|
||||||
NotificationChannel channel = new NotificationChannel(
|
NotificationChannel channel = new NotificationChannel(
|
||||||
"vpn_service",
|
channelId,
|
||||||
"VPN Service",
|
"VPN Service",
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_LOW // 设置为低优先级,避免打扰用户
|
||||||
);
|
);
|
||||||
|
channel.setDescription("通知用户 VPN 服务正在运行中");
|
||||||
|
|
||||||
|
// 注册通道
|
||||||
|
if (notificationManager != null) {
|
||||||
notificationManager.createNotificationChannel(channel);
|
notificationManager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
return new NotificationCompat.Builder(this, "vpn_service")
|
}
|
||||||
|
|
||||||
|
return new NotificationCompat.Builder(this, "vpn_service_channel")
|
||||||
.setContentTitle("VPN 服务")
|
.setContentTitle("VPN 服务")
|
||||||
.setContentText("VPN 正在运行...")
|
.setContentText("VPN 正在运行中...")
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground) // 需要替换成实际图标资源
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW) // 设置为低优先级
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,22 +2,47 @@ package com.example.studyapp.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
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 {
|
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;
|
private static File singBoxBinary, singBoxConfig;
|
||||||
|
|
||||||
public static void startSingBox(Context context) {
|
public static void startSingBox(Context context) {
|
||||||
if (isSingBoxRunning(context)) {
|
synchronized (SingboxUtil.class) { // 确保线程安全
|
||||||
Log.i("SingBox", "singbox is already running. Skipping start.");
|
if (!isLibraryLoaded) {
|
||||||
|
Log.e("SingBox", "Native library not loaded. Cannot perform StopVpn.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,34 +50,22 @@ public class SingboxUtil {
|
||||||
Log.e("SingBox", "Singbox files are missing.");
|
Log.e("SingBox", "Singbox files are missing.");
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的 JSON 验证函数示例
|
||||||
|
private static boolean isValidJson(String json) {
|
||||||
try {
|
try {
|
||||||
ProcessBuilder builder = new ProcessBuilder(
|
new org.json.JSONObject(json); // or new org.json.JSONArray(json);
|
||||||
singBoxBinary.getAbsolutePath(), "run", "-c", singBoxConfig.getAbsolutePath()
|
return true;
|
||||||
).redirectErrorStream(true);
|
} catch (org.json.JSONException ex) {
|
||||||
|
return false;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}).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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +104,7 @@ public class SingboxUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并复制配置文件
|
// 检查并复制配置文件
|
||||||
singBoxConfig = new File("/data/singbox/config.json");
|
singBoxConfig = new File(context.getCodeCacheDir(), "config.json");
|
||||||
|
|
||||||
if (!singBoxConfig.exists()) {
|
if (!singBoxConfig.exists()) {
|
||||||
try (InputStream configInputStream = context.getAssets().open("singbox/" + abi + "/config.json");
|
try (InputStream configInputStream = context.getAssets().open("singbox/" + abi + "/config.json");
|
||||||
|
@ -119,50 +132,34 @@ public class SingboxUtil {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否有正在运行的 singbox
|
|
||||||
public static boolean isSingBoxRunning(Context context) {
|
|
||||||
android.app.ActivityManager activityManager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
||||||
java.util.List<android.app.ActivityManager.RunningAppProcessInfo> 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() {
|
public static void stopSingBox() {
|
||||||
Process process = null;
|
synchronized (SingboxUtil.class) { // 防止并发冲突
|
||||||
try {
|
if (!isLibraryLoaded) {
|
||||||
// 检查是否支持 `pkill` 并停止 sing-box 进程
|
Log.e("SingBox", "Native library not loaded. Cannot perform StopVpn.");
|
||||||
ProcessBuilder builder = new ProcessBuilder("pkill", "-f", "sing-box");
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
} 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 {
|
try {
|
||||||
process.getInputStream().close();
|
Log.d("SingBox", "Invoking StopVpn method on thread: " + Thread.currentThread().getName());
|
||||||
process.getErrorStream().close();
|
int result = StopVpn();
|
||||||
} catch (IOException e) {
|
switch (result) {
|
||||||
Log.e("SingBox", "Failed to close process streams", e);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<? extends AccessibilityService> 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<? super Result> 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();
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,101 @@
|
||||||
|
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||||
|
|
||||||
|
/* package command-line-arguments */
|
||||||
|
|
||||||
|
|
||||||
|
#line 1 "cgo-builtin-export-prolog"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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 <string.h> // 声明 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 <complex.h>
|
||||||
|
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
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
||||||
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:description="@string/accessibility_service_description"
|
||||||
|
android:accessibilityEventTypes="typeAllMask"
|
||||||
|
android:accessibilityFeedbackType="feedbackSpoken"
|
||||||
|
android:notificationTimeout="100"
|
||||||
|
android:canRetrieveWindowContent="true"
|
||||||
|
android:settingsActivity="com.example.MyAccessibilitySettingsActivity" />
|
|
@ -1,4 +1,10 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// 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 {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
}
|
}
|
211
err.log
211
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-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-05-30 15:22:09.569 18506-18506 V2Ray com.example.studyapp E V2Ray files are missing, cannot start.
|
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.
|
||||||
|
|
|
@ -19,4 +19,3 @@ constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayo
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue