diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..57b2461
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..b9d18bf
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 955202d..00b7515 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -54,8 +54,7 @@
android:name=".service.MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:enabled="true"
- android:exported="false"
- android:foregroundServiceType="accessibility" >
+ android:exported="false">
diff --git a/app/src/main/java/com/example/studyapp/MainActivity.java b/app/src/main/java/com/example/studyapp/MainActivity.java
index ff3625c..cf3f21f 100644
--- a/app/src/main/java/com/example/studyapp/MainActivity.java
+++ b/app/src/main/java/com/example/studyapp/MainActivity.java
@@ -1,10 +1,8 @@
package com.example.studyapp;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.net.Uri;
-import android.net.VpnService;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
@@ -12,10 +10,7 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.provider.Settings;
-import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import android.Manifest;
@@ -30,278 +25,198 @@ 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.device.ChangeDeviceInfoUtil;
+import com.example.studyapp.utils.ClashUtil;
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;
- private static final int VPN_REQUEST_CODE = 100; // Adding the missing constant
- private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
+ private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+ private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
- 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)
- != PackageManager.PERMISSION_GRANTED
- ) {
- ActivityCompat.requestPermissions(
- this,
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
- REQUEST_CODE_STORAGE_PERMISSION
- );
- }
- } else {
- // 针对 Android 11 及更高版本检查全文件管理权限
- if (!Environment.isExternalStorageManager()) {
- // 请求权限
- Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
- intent.setData(Uri.parse("package:" + getPackageName()));
- startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE);
- }
- }
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
- if (!isNetworkAvailable(this)) {
- Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
- finish();
- }
- // 初始化按钮
- Button runScriptButton = findViewById(R.id.run_script_button);
- if (runScriptButton != null) {
- runScriptButton.setOnClickListener(v -> AutoJsUtil.runAutojsScript(this));
- } else {
- Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show();
- }
-
- Button connectButton = findViewById(R.id.connectVpnButton);
- if (connectButton != null) {
- connectButton.setOnClickListener(v -> startProxyVpn(this));
- } else {
- Toast.makeText(this, "Connect button not found", Toast.LENGTH_SHORT).show();
- }
-
- Button disconnectButton = findViewById(R.id.disconnectVpnButton);
- if (disconnectButton != null) {
- disconnectButton.setOnClickListener(v -> stopProxy(this));
- } else {
- Toast.makeText(this, "Disconnect button not found", Toast.LENGTH_SHORT).show();
- }
-
- Button modifyDeviceInfoButton = findViewById(R.id.modifyDeviceInfoButton);
- if (modifyDeviceInfoButton != null) {
- modifyDeviceInfoButton.setOnClickListener(v -> ChangeDeviceInfo.changeDeviceInfo(getPackageName(),this));
- } else {
- Toast.makeText(this, "modifyDeviceInfo button not found", Toast.LENGTH_SHORT).show();
- }
-
- Button resetDeviceInfoButton = findViewById(R.id.resetDeviceInfoButton);
- if (resetDeviceInfoButton != null) {
- resetDeviceInfoButton.setOnClickListener(v -> ChangeDeviceInfo.resetChangedDeviceInfo(getPackageName(),this));
- } else {
- 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);
+ 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)
+ != PackageManager.PERMISSION_GRANTED
+ ) {
+ ActivityCompat.requestPermissions(
+ this,
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQUEST_CODE_STORAGE_PERMISSION
+ );
+ }
+ } else {
+ // 针对 Android 11 及更高版本检查全文件管理权限
+ if (!Environment.isExternalStorageManager()) {
+ // 请求权限
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE);
+ }
}
- private void startProxyVpn(Context context) {
- if (!isNetworkAvailable(context)) {
- Toast.makeText(context, "Network is not available", Toast.LENGTH_SHORT).show();
- return;
- }
-
- if (!(context instanceof Activity)) {
- Toast.makeText(context, "Context must be an Activity", Toast.LENGTH_SHORT).show();
- return;
- }
- Activity activity = (Activity) context;
-
- try {
- startProxyServer(activity); // 在主线程中调用
- } catch (IllegalStateException e) {
- Toast.makeText(context, "Failed to start VPN: VPN Service illegal state", Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- Toast.makeText(context, "Failed to start VPN: " + (e.getMessage() != null ? e.getMessage() : "Unknown error"), Toast.LENGTH_SHORT).show();
- }
+ if (!isNetworkAvailable(this)) {
+ Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
+ finish();
}
- private void startProxyServer(Activity activity) {
- // 请求 VPN 权限
- Intent vpnPrepareIntent = VpnService.prepare(activity);
- if (vpnPrepareIntent != null) {
- // 如果尚未授予权限,请求权限,等待结果回调
- startActivityForResult(vpnPrepareIntent, VPN_REQUEST_CODE);
- } else {
- // 如果已经授予权限,直接调用 onActivityResult 模拟结果处理
- onActivityResult(VPN_REQUEST_CODE, RESULT_OK, null);
- }
+ PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(CheckAccessibilityWorker.class, 15, TimeUnit.MINUTES)
+ .build();
+ WorkManager.getInstance(this).enqueue(workRequest);
+
+ // 初始化按钮
+ Button runScriptButton = findViewById(R.id.run_script_button);
+ if (runScriptButton != null) {
+ runScriptButton.setOnClickListener(v -> AutoJsUtil.runAutojsScript(this));
+ } else {
+ Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show();
}
- private void showToastOnUiThread(Context context, String message) {
- new Handler(Looper.getMainLooper()).post(() ->
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
+ Button connectButton = findViewById(R.id.connectVpnButton);
+ if (connectButton != null) {
+ connectButton.setOnClickListener(v -> startProxyVpn(this));
+ } else {
+ Toast.makeText(this, "Connect button not found", Toast.LENGTH_SHORT).show();
}
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
- } else {
- // 提示权限被拒绝,同时允许用户重新授予权限
- showPermissionExplanationDialog();
- }
- }
+ Button disconnectButton = findViewById(R.id.disconnectVpnButton);
+ if (disconnectButton != null) {
+ disconnectButton.setOnClickListener(v -> ClashUtil.stopProxy(this));
+ } else {
+ Toast.makeText(this, "Disconnect button not found", Toast.LENGTH_SHORT).show();
}
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- switch (requestCode) {
- case ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE:
- handleStoragePermissionResult(resultCode);
- break;
- case VPN_REQUEST_CODE:
- handleVpnPermissionResult(resultCode);
- break;
- default:
- break;
- }
+ Button switchVpnButton = findViewById(R.id.switchVpnButton);
+ if (switchVpnButton != null) {
+ switchVpnButton.setOnClickListener(v -> ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170"));
+ } else {
+ Toast.makeText(this, "Disconnect button not found", Toast.LENGTH_SHORT).show();
}
- private void handleStoragePermissionResult(int resultCode) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
- Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
- finish();
- }
+ Button modifyDeviceInfoButton = findViewById(R.id.modifyDeviceInfoButton);
+ if (modifyDeviceInfoButton != null) {
+ modifyDeviceInfoButton.setOnClickListener(v -> ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170"));
+ } else {
+ Toast.makeText(this, "modifyDeviceInfo button not found", Toast.LENGTH_SHORT).show();
}
- private boolean isServiceRunning(Context context, Class> serviceClass) {
- ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- if (manager != null) {
- for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
- if (serviceClass.getName().equals(service.service.getClassName())) {
- return true;
- }
- }
- }
- return false;
+ Button resetDeviceInfoButton = findViewById(R.id.resetDeviceInfoButton);
+ if (resetDeviceInfoButton != null) {
+ resetDeviceInfoButton.setOnClickListener(v -> ChangeDeviceInfoUtil.resetChangedDeviceInfo(getPackageName(), this));
+ } else {
+ Toast.makeText(this, "resetDeviceInfo button not found", Toast.LENGTH_SHORT).show();
}
- private void stopProxy(Context context) {
- if (context == null) {
- Log.e("stopProxy", "上下文为空,无法停止服务");
- return;
- }
+ // try {
+ // if (!ClashUtil.checkProxy(this)) {
+ // startProxyVpn(this);
+ // }else {
+ // ClashUtil.switchProxyGroup("GLOBAL","us", "127.0.0.1:6170");
+ // };
+ // ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this);
+ // AutoJsUtil.runAutojsScript(this);
+ // } catch (InterruptedException e) {
+ // throw new RuntimeException(e);
+ // }
+ }
- if (!isServiceRunning(context, CustomVpnService.class)) {
- Log.w("stopProxy", "服务未运行,无法停止");
- return;
- }
-
- new Thread(() -> {
- boolean isServiceStopped = true;
- try {
- // 通过反射获取服务实例
- Object instance = ReflectionHelper.getInstance("com.example.studyapp.proxy.CustomVpnService", "instance");
- if (instance != null) {
- // 获取并调用 stopService 方法
- Method stopServiceMethod = instance.getClass().getDeclaredMethod("stopService", Intent.class);
- stopServiceMethod.invoke(instance, intent);
- Log.d("stopProxy", "服务已成功停止");
- } else {
- isServiceStopped = false;
- Log.w("stopProxy", "实例为空,服务可能未启动");
- }
- } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
- isServiceStopped = false;
- Log.e("stopProxy", "无法停止服务: " + e.getMessage(), e);
- } catch (Exception e) {
- isServiceStopped = false;
- Log.e("stopProxy", "停止服务时发生未知错误: " + e.getMessage(), e);
- }
-
- // 在主线程中更新用户提示
- String message = isServiceStopped ? "VPN 服务已停止" : "停止 VPN 服务失败";
- new Handler(Looper.getMainLooper()).post(() ->
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
- }).start();
+ private void startProxyVpn(Context context) {
+ if (!isNetworkAvailable(context)) {
+ Toast.makeText(context, "Network is not available", Toast.LENGTH_SHORT).show();
+ return;
}
- private Intent intent;
- private void handleVpnPermissionResult(int resultCode) {
- if (resultCode == RESULT_OK) {
-
- intent = new Intent(this, CustomVpnService.class);
-
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(intent);
- } else {
- startService(intent);
- }
- } catch (IllegalStateException e) {
- Log.e("handleVpnPermissionResult", "Failed to start VPN service", e);
- showToastOnUiThread(this, "Failed to start VPN service");
- }
-
- } else {
- // 其他结果代码处理逻辑
- showToastOnUiThread(this, "VPN permission denied or failed.");
- Log.e("handleVpnPermissionResult", "VPN permission denied or failed with resultCode: " + resultCode);
- }
+ if (!(context instanceof Activity)) {
+ Toast.makeText(context, "Context must be an Activity", Toast.LENGTH_SHORT).show();
+ return;
}
+ Activity activity = (Activity) context;
- private void showPermissionExplanationDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("Permission Required")
- .setMessage("Storage Permission is required for the app to function. Please enable it in Settings.")
- .setPositiveButton("Go to Settings", (dialog, which) -> {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- Uri uri = Uri.fromParts("package", getPackageName(), null);
- intent.setData(uri);
- startActivity(intent);
- })
- .setNegativeButton("Cancel", (dialog, which) -> finish())
- .show();
+ try {
+ ClashUtil.startProxy(context); // 在主线程中调用
+ } catch (IllegalStateException e) {
+ Toast.makeText(context, "Failed to start VPN: VPN Service illegal state", Toast.LENGTH_SHORT).show();
+ } catch (Exception e) {
+ Toast.makeText(context, "Failed to start VPN: " + (e.getMessage() != null ? e.getMessage() : "Unknown error"), Toast.LENGTH_SHORT).show();
}
+ }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (AutoJsUtil.scriptResultReceiver != null) {
- unregisterReceiver(AutoJsUtil.scriptResultReceiver);
- }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
+ } else {
+ // 提示权限被拒绝,同时允许用户重新授予权限
+ showPermissionExplanationDialog();
+ }
}
+ }
- private boolean isNetworkAvailable(Context context) {
- ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (connectivityManager != null) {
- Network network = connectivityManager.getActiveNetwork();
- if (network != null) {
- NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
- return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
- }
- }
- return false;
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ switch (requestCode) {
+ case ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE:
+ handleStoragePermissionResult(resultCode);
+ break;
+ default:
+ break;
}
+ }
+
+ private void handleStoragePermissionResult(int resultCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
+ Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+
+ private void showPermissionExplanationDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Permission Required")
+ .setMessage("Storage Permission is required for the app to function. Please enable it in Settings.")
+ .setPositiveButton("Go to Settings", (dialog, which) -> {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ startActivity(intent);
+ })
+ .setNegativeButton("Cancel", (dialog, which) -> finish())
+ .show();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (AutoJsUtil.scriptResultReceiver != null) {
+ unregisterReceiver(AutoJsUtil.scriptResultReceiver);
+ }
+ }
+
+ private boolean isNetworkAvailable(Context context) {
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivityManager != null) {
+ Network network = connectivityManager.getActiveNetwork();
+ if (network != null) {
+ NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
+ return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ }
+ }
+ return false;
+ }
}
diff --git a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java
similarity index 95%
rename from app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java
rename to app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java
index 114f7c1..91298ee 100644
--- a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfo.java
+++ b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java
@@ -1,16 +1,9 @@
package com.example.studyapp.device;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.ProxyInfo;
import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-import com.example.studyapp.R;
-import com.example.studyapp.utils.ReflectionHelper;
import com.example.studyapp.utils.ShellUtils;
import org.json.JSONObject;
@@ -18,7 +11,7 @@ import org.json.JSONObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-public class ChangeDeviceInfo {
+public class ChangeDeviceInfoUtil {
public static void changeDeviceInfo(String current_pkg_name,Context context) {
// 指定包名优先级高于全局
@@ -81,12 +74,12 @@ public class ChangeDeviceInfo {
} catch (Throwable e) {
- Log.e("ChangeDeviceInfo", "Error occurred while changing device info", e);
+ Log.e("ChangeDeviceInfoUtil", "Error occurred while changing device info", e);
throw new RuntimeException("Error occurred in changeDeviceInfo", e);
}
if (!ShellUtils.hasRootAccess()) {
- Log.e("ChangeDeviceInfo", "Root access is required to execute system property changes");
+ Log.e("ChangeDeviceInfoUtil", "Root access is required to execute system property changes");
return;
}
diff --git a/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java b/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java
deleted file mode 100644
index 405e9bc..0000000
--- a/app/src/main/java/com/example/studyapp/proxy/CustomVpnService.java
+++ /dev/null
@@ -1,339 +0,0 @@
-package com.example.studyapp.proxy;
-
-import static com.example.studyapp.utils.IpUtil.isValidIpAddress;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-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 java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-
-public class CustomVpnService extends VpnService {
-
- private static final int PREFIX_LENGTH = 28; // 子网掩码
-
- private Thread vpnTrafficThread; // 保存线程引用
- private ParcelFileDescriptor vpnInterface; // TUN 接口描述符
-
- private static CustomVpnService instance;
-
- private static final int NOTIFICATION_ID = 1;
-
- private volatile boolean isVpnActive = false; // 标志位控制数据包处理逻辑
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- isVpnActive = true; // 服务启动时激活
- // 开始前台服务
- startForeground(NOTIFICATION_ID, createNotification());
- try {
- // 检查 Singbox 是否已启动,避免重复进程
-
- SingboxUtil.startSingBox(getApplicationContext());
- // 启动 VPN 流量服务
- startVpn();
- } catch (Exception e) {
- Log.e("CustomVpnService", "Error in onStartCommand: " + e.getMessage(), e);
- stopSelf(); // 发生异常时停止服务
- }
- return START_STICKY;
- }
-
- private void startVpn() {
- try {
- // 配置虚拟网卡
- Builder builder = new Builder();
-
- // 不再需要手动验证 TUN_ADDRESS 和 PREFIX_LENGTH
- // 直接使用系统权限建立虚拟网卡,用于 TUN 接口和流量捕获
- builder.addAddress(ConfigLoader.getTunnelAddress(this), PREFIX_LENGTH); // 保证 TUN 接口 IP 地址仍与 singbox 配置文件保持一致
- builder.addRoute("0.0.0.0", 0); // 捕获所有 IPv4 流量
-
- // DNS 部分,如果有需要,也可以简化或直接保留 singbox 配置提供的
- List dnsServers = getSystemDnsServers();
- if (dnsServers.isEmpty()) {
- // 如果未能从系统中获取到 DNS 地址,添加备用默认值
- builder.addDnsServer("8.8.8.8"); // Google DNS
- builder.addDnsServer("8.8.4.4");
- } else {
- for (String dnsServer : dnsServers) {
- if (isValidIpAddress(dnsServer)) {
- builder.addDnsServer(dnsServer);
- }
- }
- }
-
- builder.setBlocking(true);
- // 直接建立 TUN 虚拟接口
- vpnInterface = builder.establish();
- if (vpnInterface == null) {
- Log.e(
- "CustomVpnService",
- "builder.establish() returned null. Check VpnService.Builder configuration and system state."
- );
- throw new IllegalStateException("VPN Interface establishment failed");
- }
-
- // 核心:启动流量转发服务,此后转发逻辑由 singbox 接管
- new Thread(() -> handleVpnTraffic(vpnInterface)).start();
-
- } catch (Exception e) {
- // 增强日志描述信息,方便调试
- Log.e(
- "CustomVpnService",
- "startVpn failed: " + e.getMessage(),
- e
- );
- }
- }
-
- // 工具方法:判断 IP 地址是否合法
- private void handleVpnTraffic(ParcelFileDescriptor vpnInterface) {
- // 启动处理流量的线程
- vpnTrafficThread = new Thread(() -> {
- if (vpnInterface == null || !vpnInterface.getFileDescriptor().valid()) {
- Log.e("CustomVpnService", "ParcelFileDescriptor is invalid!");
- return;
- }
-
- byte[] packetData = new byte[32767]; // 数据包缓冲区
-
- try (FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
- FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor())) {
-
- while (!Thread.currentThread().isInterrupted() && isVpnActive) { // 检查线程是否已中断
- int length;
- try {
- length = inStream.read(packetData);
- if (length == -1) {
- break; // 读取完成退出
- }
- } catch (IOException e) {
- Log.e("CustomVpnService", "Error reading packet", e);
- break; // 读取出错退出循环
- }
-
- if (length > 0) {
- boolean handled = processPacket(packetData, length);
- if (!handled) {
- outStream.write(packetData, 0, length); // 未处理的包写回
- }
- }
- }
- } catch (IOException e) {
- Log.e("CustomVpnService", "Error handling VPN traffic", e);
- } finally {
- try {
- vpnInterface.close();
- } catch (IOException e) {
- Log.e("CustomVpnService", "Failed to close vpnInterface", e);
- }
- }
- });
- vpnTrafficThread.start();
- }
-
- private boolean processPacket(byte[] packetData, int length) {
- if (!isVpnActive) {
- Log.w("CustomVpnService", "VPN is not active. Skipping packet processing.");
- return false;
- }
- if (packetData == null || length <= 0 || length > packetData.length) {
- Log.w("CustomVpnService", "Invalid packetData or length");
- return false;
- }
-
- try {
- boolean isTcpPacket = checkIfTcpPacket(packetData);
- boolean isDnsPacket = checkIfDnsRequest(packetData);
- Log.d("CustomVpnService", "Packet Info: TCP=" + isTcpPacket + ", DNS=" + isDnsPacket + ", Length=" + length);
-
- if (isTcpPacket || isDnsPacket) {
- Log.i("CustomVpnService", "Forwarding to Singbox. Packet Length: " + length);
- return true;
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- Log.e("CustomVpnService", "Malformed packet data: out of bounds", e);
- } catch (IllegalArgumentException e) {
- Log.e("CustomVpnService", "Invalid packet content", e);
- } catch (Exception e) {
- Log.e("CustomVpnService", "Unexpected error during packet processing. Packet Length: " + length, e);
- }
- return false;
- }
-
- @Override
- public boolean stopService(Intent name) {
- isVpnActive = false; // 服务停止时停用
- super.stopService(name);
-
- // 停止处理数据包的线程
- if (vpnTrafficThread != null && vpnTrafficThread.isAlive()) {
- vpnTrafficThread.interrupt(); // 中断线程
- try {
- vpnTrafficThread.join(); // 等待线程停止
- } catch (InterruptedException e) {
- Log.e("CustomVpnService", "Error while stopping vpnTrafficThread", e);
- Thread.currentThread().interrupt(); // 重新设置当前线程的中断状态
- }
- vpnTrafficThread = null; // 清空线程引用
- }
-
- // 关闭 VPN 接口
- if (vpnInterface != null) {
- try {
- vpnInterface.close();
- } catch (IOException e) {
- Log.e("CustomVpnService", "Error closing VPN interface: " + e.getMessage(), e);
- }
- vpnInterface = null; // 避免资源泄露
- }
-
- // 停止 Singbox 服务
- SingboxUtil.stopSingBox();
- Log.i("CustomVpnService", "VPN 服务已停止");
- return true;
- }
-
- @Override
- public void onDestroy() {
- isVpnActive = false; // 服务销毁时停用
- super.onDestroy();
-
- // 停止处理数据包的线程
- if (vpnTrafficThread != null && vpnTrafficThread.isAlive()) {
- vpnTrafficThread.interrupt(); // 中断线程
- try {
- vpnTrafficThread.join(); // 等待线程停止
- } catch (InterruptedException e) {
- Log.e("CustomVpnService", "Error while stopping vpnTrafficThread", e);
- Thread.currentThread().interrupt(); // 重新设置当前线程的中断状态
- }
- vpnTrafficThread = null; // 清空线程引用
- }
-
- // 关闭 VPN 接口
- if (vpnInterface != null) {
- try {
- vpnInterface.close();
- } catch (IOException e) {
- Log.e("CustomVpnService", "Error closing VPN interface: " + e.getMessage(), e);
- }
- vpnInterface = null; // 避免资源泄露
- }
-
- // 停止 Singbox 服务
- SingboxUtil.stopSingBox();
- Log.i("CustomVpnService", "VPN 服务已销毁");
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- instance = this; // 在创建时将实例存储到静态字段
- }
-
- private boolean checkIfTcpPacket(byte[] packetData) {
- // IPv4 数据包最前面 1 字节的前 4 位是版本号
- int version = (packetData[0] >> 4) & 0xF;
- if (version != 4) {
- return false; // 非 IPv4,不处理
- }
-
- // IPv4 中 Protocol 字段位于位置 9,值为 6 表示 TCP 协议
- int protocol = packetData[9] & 0xFF; // 取无符号位
- return protocol == 6; // 6 表示 TCP
- }
-
- private boolean checkIfDnsRequest(byte[] packetData) {
- // IPv4 UDP 协议号为 17
- int protocol = packetData[9] & 0xFF;
- if (protocol != 17) {
- return false; // 不是 UDP
- }
-
- // UDP 源端口在 IPv4 头部后的第 0-1 字节(总长度 IPv4 Header 20 字节 + UDP Offset)
- // IPv4 头长度在第一个字节后四位
- int ipHeaderLength = (packetData[0] & 0x0F) * 4;
- int sourcePort = ((packetData[ipHeaderLength] & 0xFF) << 8) | (packetData[ipHeaderLength + 1] & 0xFF);
- int destPort = ((packetData[ipHeaderLength + 2] & 0xFF) << 8) | (packetData[ipHeaderLength + 3] & 0xFF);
-
- // 检查是否是 UDP 的 DNS 端口 (53)
- return sourcePort == 53 || destPort == 53;
- }
-
- private List getSystemDnsServers() {
- List dnsServers = new ArrayList<>();
- try {
- String command = "getprop | grep dns";
- Process process = Runtime.getRuntime().exec(command);
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.contains("dns") && line.contains(":")) {
- String dns = line.split(":")[1].trim();
- if (isValidIpAddress(dns)) { // 添加有效性检查
- dnsServers.add(dns);
- }
- }
- }
- }
- } catch (IOException e) {
- // 捕获问题日志
- Log.e("CustomVpnService", "Error while fetching DNS servers: " + e.getMessage(), e);
- }
- // 添加默认 DNS 在无效情况下
- if (dnsServers.isEmpty()) {
- dnsServers.add("8.8.8.8");
- dnsServers.add("8.8.4.4");
- dnsServers.add("1.1.1.1");
- }
- return dnsServers;
- }
-
- 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(
- channelId,
- "VPN Service",
- NotificationManager.IMPORTANCE_LOW // 设置为低优先级,避免打扰用户
- );
- channel.setDescription("通知用户 VPN 服务正在运行中");
-
- // 注册通道
- if (notificationManager != null) {
- notificationManager.createNotificationChannel(channel);
- }
- }
-
- return new NotificationCompat.Builder(this, "vpn_service_channel")
- .setContentTitle("VPN 服务")
- .setContentText("VPN 正在运行中...")
- .setSmallIcon(R.drawable.ic_launcher_foreground) // 需要替换成实际图标资源
- .setPriority(NotificationCompat.PRIORITY_LOW) // 设置为低优先级
- .build();
- }
-
- public static CustomVpnService getInstance() {
- return instance;
- }
-}
diff --git a/app/src/main/java/com/example/studyapp/utils/ClashUtil.java b/app/src/main/java/com/example/studyapp/utils/ClashUtil.java
new file mode 100644
index 0000000..2d4e4ac
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/utils/ClashUtil.java
@@ -0,0 +1,133 @@
+package com.example.studyapp.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+import androidx.core.content.ContextCompat;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Credentials;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @Time: 2025/6/9 11:13
+ * @Creator: 初屿贤
+ * @File: ClashUtil
+ * @Project: study.App
+ * @Description:
+ */
+public class ClashUtil {
+
+ public static void startProxy(Context context) {
+ Intent intent = new Intent("com.github.kr328.clash.intent.action.SESSION_CREATE");
+ intent.putExtra("profile", "default"); // 可选择您在 Clash 中配置的 Profile
+ context.sendBroadcast(intent);
+ }
+
+ public static void stopProxy(Context context) {
+ new Thread(() -> {
+ Intent intent = new Intent("com.github.kr328.clash.intent.action.SESSION_DESTROY");
+ context.sendBroadcast(intent);
+ }).start();
+ }
+
+ private static volatile boolean isRunning = false;
+
+ private static final BroadcastReceiver clashStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ isRunning = intent.getBooleanExtra("isRunning", false);
+ Log.d("ClashUtil", "Clash Status: " + isRunning);
+ }
+ };
+
+ public static boolean checkProxy(Context context) throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ checkClashStatus(context, latch);
+ latch.await(); // 等待广播接收器更新状态
+ return isRunning;
+ }
+
+ public static void checkClashStatus(Context context, CountDownLatch latch) {
+ IntentFilter intentFilter = new IntentFilter("com.github.kr328.clash.intent.action.SESSION_STATE");
+ BroadcastReceiver clashStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ isRunning = intent.getBooleanExtra("isRunning", false);
+ Log.d("ClashUtil", "Clash Status: " + isRunning);
+ latch.countDown(); // 状态更新完成,释放锁
+ }
+ };
+ ContextCompat.registerReceiver(context, clashStatusReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
+ Intent queryIntent = new Intent("com.github.kr328.clash.intent.action.SESSION_QUERY");
+ context.sendBroadcast(queryIntent);
+ }
+
+ public static void unregisterReceiver(Context context) {
+ context.unregisterReceiver(clashStatusReceiver);
+ }
+
+ public static void switchProxyGroup(String groupName, String proxyName, String controllerUrl) {
+ if (groupName == null || groupName.trim().isEmpty() || proxyName == null || proxyName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Group name and proxy name must not be empty");
+ }
+ if (controllerUrl == null || !controllerUrl.matches("^https?://.*")) {
+ throw new IllegalArgumentException("Invalid controller URL");
+ }
+
+ OkHttpClient client = new OkHttpClient();
+ JSONObject json = new JSONObject();
+ try {
+ json.put("name", proxyName);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ String jsonBody = json.toString();
+
+ MediaType JSON = MediaType.get("application/json; charset=utf-8");
+ RequestBody requestBody = RequestBody.create(JSON, jsonBody);
+
+ HttpUrl url = HttpUrl.parse(controllerUrl)
+ .newBuilder()
+ .addPathSegments("proxies/" + groupName)
+ .build();
+
+ Request request = new Request.Builder()
+ .url(url)
+ .put(requestBody)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ e.printStackTrace();
+ System.out.println("Failed to switch proxy: " + e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ try {
+ if (response.body() != null) {
+ System.out.println("Switch proxy response: " + response.body().string());
+ } else {
+ System.out.println("Response body is null");
+ }
+ } finally {
+ response.close();
+ }
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java b/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java
deleted file mode 100644
index 705a32d..0000000
--- a/app/src/main/java/com/example/studyapp/utils/SingboxUtil.java
+++ /dev/null
@@ -1,166 +0,0 @@
-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) {
- 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;
- }
- 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 {
- new org.json.JSONObject(json); // or new org.json.JSONArray(json);
- return true;
- } catch (org.json.JSONException ex) {
- return false;
- }
- }
-
- // 检查并复制 singbox 必需文件
- public static boolean ensureSingBoxFilesExist(Context context) {
- synchronized (SingboxUtil.class) {
- try {
- // 检查并复制 singbox 可执行文件
- String abi = Build.SUPPORTED_ABIS[0]; // 获取当前设备支持的 ABI 架构
- singBoxBinary = new File(context.getCodeCacheDir(), "singbox");
-
- // 复制二进制文件
- if (!singBoxBinary.exists()) {
- try (InputStream binaryInputStream = context.getAssets().open("singbox/" + abi + "/sing-box");
- FileOutputStream binaryOutputStream = new FileOutputStream(singBoxBinary)) {
- byte[] buffer = new byte[1024];
- int length;
- while ((length = binaryInputStream.read(buffer)) > 0) {
- binaryOutputStream.write(buffer, 0, length);
- }
- Log.i("SingBox", "Copied singbox binary to: " + singBoxBinary.getAbsolutePath());
- } catch (Exception e) {
- Log.e("SingboxUtil", "Failed to copy singbox binary", e);
- return false;
- }
- }
-
- // 设置执行权限
- singBoxBinary.setExecutable(true);
- singBoxBinary.setReadable(true);
- singBoxBinary.setWritable(true);
-
- if (!singBoxBinary.canExecute()) {
- Log.e("SingboxUtil", "Binary file does not have execute permission. Aborting start.");
- return false;
- }
-
- // 检查并复制配置文件
- singBoxConfig = new File(context.getCodeCacheDir(), "config.json");
-
- if (!singBoxConfig.exists()) {
- try (InputStream configInputStream = context.getAssets().open("singbox/" + abi + "/config.json");
- FileOutputStream configOutputStream = new FileOutputStream(singBoxConfig)) {
- byte[] buffer = new byte[1024];
- int length;
- while ((length = configInputStream.read(buffer)) > 0) {
- configOutputStream.write(buffer, 0, length);
- }
- Log.i("SingBox", "Copied singbox config.json to: " + singBoxConfig.getAbsolutePath());
- } catch (Exception e) {
- Log.e("SingboxUtil", "Failed to copy config.json", e);
- return false;
- }
- }
-
- singBoxConfig.setReadable(true);
- singBoxConfig.setWritable(true);
-
- return true;
- } catch (Exception e) {
- Log.e("SingboxUtil", "Error in ensureSingBoxFilesExist method", e);
- }
- }
- return false;
- }
-
- public static void stopSingBox() {
- synchronized (SingboxUtil.class) { // 防止并发冲突
- if (!isLibraryLoaded) {
- Log.e("SingBox", "Native library not loaded. Cannot perform StopVpn.");
- return;
- }
-
- 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);
- }
- }
- }
-}
diff --git a/app/src/main/java/com/example/studyapp/utils/V2rayUtil.java b/app/src/main/java/com/example/studyapp/utils/V2rayUtil.java
deleted file mode 100644
index f7136c8..0000000
--- a/app/src/main/java/com/example/studyapp/utils/V2rayUtil.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package com.example.studyapp.utils;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
-public class V2rayUtil {
- private static File v2rayConfig, v2rayBinary;
-
- public static void startV2Ray(Context context) {
- try {
- // 确保文件存在并准备就绪
- if (!ensureV2RayFilesExist(context)) {
- Log.e("V2Ray", "V2Ray files are missing, cannot start.");
- return;
- }
-
-
- // 构建命令
- ProcessBuilder builder = new ProcessBuilder(v2rayBinary.getAbsolutePath(), "-config", v2rayConfig.getAbsolutePath()).redirectErrorStream(true);
-
- // 启动进程
- try {
- Process process = builder.start();
- } catch (IOException e) {
- Log.e("V2Ray", "Failed to start the process", e);
- return;
- }
-
- // 日志输出
- Log.i("V2Ray", "V2Ray service started");
- } catch (Exception e) {
- Log.e("V2Ray", "Failed to start V2Ray core", e);
- }
- }
-
- public static boolean ensureV2RayFilesExist(Context context) {
-
- synchronized (V2rayUtil.class) {
- try {
- // 检查并复制 v2ray 可执行文件
- String abi = Build.SUPPORTED_ABIS[0]; // 获取当前设备支持的 ABI 架构
- v2rayBinary = new File(context.getCodeCacheDir(), "v2ray");
-
- if (!v2rayBinary.exists()) {
- InputStream binaryInputStream = context.getAssets().open("v2ray/" + abi + "/v2ray");
- FileOutputStream binaryOutputStream = new FileOutputStream(v2rayBinary);
- try {
- byte[] buffer = new byte[1024];
- int length;
- while ((length = binaryInputStream.read(buffer)) > 0) {
- binaryOutputStream.write(buffer, 0, length);
- }
- Log.i("V2Ray", "Copied v2ray binary to: " + v2rayBinary.getAbsolutePath());
- } catch (Exception e) {
- Log.e("V2rayUtil", "Failed to copy v2ray binary", e);
- return false;
- } finally {
- binaryInputStream.close();
- binaryOutputStream.close();
- }
- }
- v2rayBinary.setExecutable(true, false);
- v2rayBinary.setReadable(true, false);
- v2rayBinary.setWritable(true, false);
-
- // 检查文件是否已经具有可执行权限
- if (!v2rayBinary.canExecute()) {
- Log.e("V2rayUtil", "Binary file does not have execute permission. Aborting start.");
- return false;
- }
-
- // 检查并复制 config.json 文件
- v2rayConfig = new File("/data/v2ray/config.json");
-
- File v2rayDirectory = v2rayConfig.getParentFile();
- if (v2rayDirectory != null && !v2rayDirectory.exists()) {
- Log.e("V2rayUtil", "Failed to find directory: " + v2rayDirectory.getAbsolutePath());
- return false; // 无法创建目录时直接返回
- }
-
- if (!v2rayConfig.exists()) {
- InputStream configInputStream = context.getAssets().open("v2ray/" + abi + "/config.json");
- FileOutputStream configOutputStream = new FileOutputStream(v2rayConfig);
- try {
- byte[] buffer = new byte[1024];
- int length;
- while ((length = configInputStream.read(buffer)) > 0) {
- configOutputStream.write(buffer, 0, length);
- }
- Log.i("V2Ray", "Copied v2ray config.json to: " + v2rayConfig.getAbsolutePath());
- } catch (Exception e) {
- Log.e("V2rayUtil", "Failed to copy config.json", e);
- return false;
- } finally {
- configInputStream.close();
- configOutputStream.close();
- }
- }
- v2rayConfig.setReadable(true, false);
- v2rayConfig.setWritable(true, false);
-
- return true;
- } catch (IOException e) {
- Log.e("V2Ray", "Failed to prepare V2Ray files", e);
- return false;
- }
- }
- }
-
- public static void stopV2Ray() {
- // 如果二进制文件不存在或不可执行,直接返回
- if (v2rayBinary == null || !v2rayBinary.exists() || !v2rayBinary.canExecute()) {
- Log.e("V2Ray", "v2rayBinary is either null, does not exist, or is not executable: " +
- (v2rayBinary != null ? v2rayBinary.getAbsolutePath() : "null"));
- return;
- }
-
- // 创建新线程来处理停止任务
- new Thread(() -> {
- try {
- // 判断是否有运行中的 v2ray 进程
- if (isV2rayRunning()) {
- String command = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "ps -A" : "ps";
- Process psProcess = Runtime.getRuntime().exec(command); // 列出所有进程
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(psProcess.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- // 检查是否是 v2ray 进程
- if (line.contains("v2ray")) {
- String[] parts = line.trim().split("\\s+");
- if (parts.length > 1) {
- String pid = parts[1]; // 获取 PID
- Log.i("V2Ray", "Found V2Ray process, PID: " + pid);
-
- // 发出 kill 指令以终止进程
- Process killProcess = new ProcessBuilder("kill", "-9", pid).start();
- killProcess.waitFor(); // 等待命令完成
- Log.i("V2Ray", "V2Ray stopped successfully.");
- return; // 停止任务完成后退出
- }
- }
- }
- }
- }
-
- Log.i("V2Ray", "No V2Ray process is currently running.");
- } catch (IOException | InterruptedException e) {
- Log.e("V2Ray", "Error while stopping V2Ray: " + e.getMessage(), e);
- }
- }).start();
- }
-
- public static boolean isV2rayRunning() {
- try {
- String command = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "ps -A" : "ps";
- Process process = Runtime.getRuntime().exec(command);
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.startsWith("v2ray")) { // 更精确匹配进程描述
- Log.i("CustomVpnService", "V2Ray process found: " + line);
- return true;
- }
- }
- }
- Log.i("CustomVpnService", "No V2Ray process is running.");
- } catch (IOException e) {
- Log.e("CustomVpnService", "Error checking V2Ray process: " + e.getMessage(), e);
- }
- return false;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/jniLibs/arm64-v8a/libnative.so b/app/src/main/jniLibs/arm64-v8a/libnative.so
index 6bebff1..f87c13d 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/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 79326d0..61572b9 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,48 +1,60 @@
-
+
-
+
-
+
-
+
-
+
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9a07d50..ec172df 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
- study.App
-
\ No newline at end of file
+ study_app
+ This is an accessibility service.
+
diff --git a/app/src/main/res/xml/AccessibilityServiceConfig.xml b/app/src/main/res/xml/accessibility_service_config.xml
similarity index 96%
rename from app/src/main/res/xml/AccessibilityServiceConfig.xml
rename to app/src/main/res/xml/accessibility_service_config.xml
index 4fcb63e..7169410 100644
--- a/app/src/main/res/xml/AccessibilityServiceConfig.xml
+++ b/app/src/main/res/xml/accessibility_service_config.xml
@@ -4,4 +4,4 @@
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
- android:settingsActivity="com.example.MyAccessibilitySettingsActivity" />
\ No newline at end of file
+ android:settingsActivity="com.example.MyAccessibilitySettingsActivity" />
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index ea806d8..a1be420 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -9,5 +9,6 @@
47.236.153.142
8.211.204.20
+ 127.0.0.1
-
\ No newline at end of file
+