Refactor and enhance ShellUtils; add ClassUtils utility.
Refactored ShellUtils to improve safety, error handling, and modularity, including checks for command safety, better error logging, and execution timeout handling. Introduced a new ClassUtils utility for operations on classes, such as retrieving short class names, converting between primitive and wrapper types, and handling interfaces and superclasses.
This commit is contained in:
parent
208e977d2f
commit
931944167e
|
@ -1 +1 @@
|
|||
study.App
|
||||
V2rayUtil.java
|
|
@ -4,10 +4,10 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-05-26T09:42:51.679223700Z">
|
||||
<DropdownSelection timestamp="2025-05-29T05:57:33.756287400Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=10.255.31.151:5555;connection=cafb3fe2" />
|
||||
<DeviceId pluginId="Default" identifier="serial=8.217.74.194:1151;connection=7a73959e" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
|
|
@ -2,12 +2,12 @@ package com.example.studyapp;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ServiceConnection;
|
||||
import android.net.Uri;
|
||||
import android.net.VpnService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
|
@ -29,18 +29,12 @@ import androidx.core.app.ActivityCompat;
|
|||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.studyapp.autoJS.AutoJsUtil;
|
||||
import com.example.studyapp.proxy.CustomVpnService;
|
||||
import com.example.studyapp.request.ScriptResultRequest;
|
||||
import com.example.studyapp.service.CloudPhoneManageService;
|
||||
import com.example.studyapp.utils.ReflectionHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
|
||||
|
@ -48,12 +42,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
|
||||
|
||||
private static final int REQUEST_CODE_VPN = 2;
|
||||
|
||||
private BroadcastReceiver scriptResultReceiver;
|
||||
|
||||
private ActivityResultLauncher<Intent> vpnRequestLauncher;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -72,7 +60,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
} else {
|
||||
// 针对 Android 11 及更高版本检查全文件管理权限
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
// 请求权限
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
|
@ -80,13 +68,32 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
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 -> runAutojsScript());
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void startProxyVpn(Context context) {
|
||||
|
@ -130,55 +137,103 @@ public class MainActivity extends AppCompatActivity {
|
|||
@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, "Permissions granted", Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (!isNetworkAvailable(this)) {
|
||||
Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动 VPN 服务
|
||||
startProxyVpn(this);
|
||||
Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// 提示权限被拒绝,同时允许用户重新授予权限
|
||||
showPermissionExplanationDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
|
||||
if (!isNetworkAvailable(this)) {
|
||||
Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
// 启动 VPN 服务
|
||||
startProxyVpn(this);
|
||||
} else {
|
||||
// 权限未授予,可提示用户
|
||||
Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
|
||||
switch (requestCode) {
|
||||
case ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE:
|
||||
handleStoragePermissionResult(resultCode);
|
||||
break;
|
||||
case VPN_REQUEST_CODE:
|
||||
handleVpnPermissionResult(resultCode);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||
// Permission granted, now you can start your VpnService
|
||||
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 stopProxy(Context context) {
|
||||
if (context == null) {
|
||||
Log.e("stopProxy", "上下文为空,无法停止服务");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isServiceStopped = true;
|
||||
try {
|
||||
Object instance = ReflectionHelper.getInstance("com.example.studyapp.proxy.CustomVpnService", "instance");
|
||||
if (instance != null) {
|
||||
// 尝试获取 onDestroy 方法并调用
|
||||
Method onDestroyMethod = instance.getClass().getMethod("onDestroy");
|
||||
onDestroyMethod.invoke(instance);
|
||||
Log.d("stopProxy", "服务已成功停止");
|
||||
} else {
|
||||
isServiceStopped = false;
|
||||
Log.w("stopProxy", "实例为空,服务可能未启动");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
isServiceStopped = false;
|
||||
Log.e("stopProxy", "服务未提供 onDestroy 方法: " + e.getMessage(), e);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
isServiceStopped = false;
|
||||
Log.e("stopProxy", "无法调用 onDestroy 方法: " + 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());
|
||||
}
|
||||
|
||||
private void handleVpnPermissionResult(int resultCode) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
|
||||
Intent intent = new Intent(this, CustomVpnService.class);
|
||||
|
||||
if (intent == null) {
|
||||
Log.e("handleVpnPermissionResult", "Intent is null. Cannot start service.");
|
||||
showToastOnUiThread(this, "Failed to start VPN service due to null intent.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
this.startForegroundService(intent);
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
this.startService(intent);
|
||||
startService(intent);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,8 +254,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (scriptResultReceiver != null) {
|
||||
unregisterReceiver(scriptResultReceiver);
|
||||
if (AutoJsUtil.scriptResultReceiver != null) {
|
||||
unregisterReceiver(AutoJsUtil.scriptResultReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,114 +263,12 @@ public class MainActivity extends AppCompatActivity {
|
|||
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);
|
||||
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void runAutojsScript() {
|
||||
// 定义脚本文件路径
|
||||
File scriptFile = new File(Environment.getExternalStorageDirectory(), "脚本/chromium.js");
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!scriptFile.exists()) {
|
||||
Toast.makeText(this, "Script file not found: " + scriptFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 Auto.js 应用是否安装
|
||||
if (!isAppInstalled("org.autojs.autojs6")) {
|
||||
Toast.makeText(this, "Auto.js app not installed", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备启动 Auto.js 的 Intent
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("org.autojs.autojs6", "org.autojs.autojs.external.open.RunIntentActivity");
|
||||
intent.putExtra("path", scriptFile.getAbsolutePath()); // 传递脚本路径
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
// 启动 Auto.js
|
||||
try {
|
||||
// 模拟:通过广播监听脚本运行结果
|
||||
registerScriptResultReceiver(); // 注册结果回调监听(假设脚本通过广播返回结果)
|
||||
startActivity(intent);
|
||||
Toast.makeText(this, "Running script: " + scriptFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(this, "Failed to run script", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查目标应用是否安装
|
||||
private boolean isAppInstalled(String packageName) {
|
||||
PackageManager packageManager = getPackageManager();
|
||||
try {
|
||||
packageManager.getPackageInfo(packageName, 0);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerScriptResultReceiver() {
|
||||
// 创建广播接收器
|
||||
scriptResultReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// 获取脚本运行结果(假设结果通过 "result" 键返回)
|
||||
String scriptResult = intent.getStringExtra("result");
|
||||
if (scriptResult != null && !scriptResult.isEmpty()) {
|
||||
// 处理结果并发送给服务端
|
||||
sendResultToServer(scriptResult);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 注册接收器(假设 Auto.js 广播动作为 "org.autojs.SCRIPT_FINISHED")
|
||||
IntentFilter filter = new IntentFilter("org.autojs.SCRIPT_FINISHED");
|
||||
|
||||
// 使用 ContextCompat.registerReceiver 注册,并设置为 RECEIVER_EXPORTED
|
||||
ContextCompat.registerReceiver(
|
||||
this, // 当前上下文
|
||||
scriptResultReceiver, // 自定义的 BroadcastReceiver
|
||||
filter, // IntentFilter
|
||||
ContextCompat.RECEIVER_EXPORTED // 设置为非导出广播接收器
|
||||
);
|
||||
}
|
||||
|
||||
private void sendResultToServer(String scriptResult) {
|
||||
// 使用 Retrofit 或 HttpURLConnection 实现服务端 API 调用
|
||||
Toast.makeText(this, "Sending result to server: " + scriptResult, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// 示例:用 Retrofit 设置服务端请求
|
||||
// 创建 Retrofit 的实例
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://your-server-url.com/") // 替换为服务端 API 地址
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
// 定义 API 接口
|
||||
CloudPhoneManageService api = retrofit.create(CloudPhoneManageService.class);
|
||||
|
||||
// 构建请求体并发送请求
|
||||
Call<Void> call = api.sendScriptResult(new ScriptResultRequest(scriptResult)); // 假设参数是 com.example.studyapp.request.ScriptResultRequest 对象
|
||||
call.enqueue(new Callback<Void>() {
|
||||
@Override
|
||||
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(MainActivity.this, "Result sent successfully", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(MainActivity.this, "Failed to send result: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Void> call, Throwable t) {
|
||||
Toast.makeText(MainActivity.this, "Error sending result: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package com.example.studyapp.autoJS;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.example.studyapp.MainActivity;
|
||||
import com.example.studyapp.request.ScriptResultRequest;
|
||||
import com.example.studyapp.service.CloudPhoneManageService;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class AutoJsUtil {
|
||||
|
||||
public static BroadcastReceiver scriptResultReceiver;
|
||||
|
||||
public static void runAutojsScript(Context context) {
|
||||
// 定义脚本文件路径
|
||||
File scriptFile = new File(Environment.getExternalStorageDirectory(), "脚本/chromium.js");
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!scriptFile.exists()) {
|
||||
Toast.makeText(context, "Script file not found: " + scriptFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 Auto.js 应用是否安装
|
||||
if (!isAppInstalled("org.autojs.autojs6",context.getPackageManager())) {
|
||||
Toast.makeText(context, "Auto.js app not installed", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备启动 Auto.js 的 Intent
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("org.autojs.autojs6", "org.autojs.autojs.external.open.RunIntentActivity");
|
||||
intent.putExtra("path", scriptFile.getAbsolutePath()); // 传递脚本路径
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
// 启动 Auto.js
|
||||
try {
|
||||
// 模拟:通过广播监听脚本运行结果
|
||||
registerScriptResultReceiver(context); // 注册结果回调监听(假设脚本通过广播返回结果)
|
||||
context.startActivity(intent);
|
||||
Toast.makeText(context, "Running script: " + scriptFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, "Failed to run script", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查目标应用是否安装
|
||||
public static boolean isAppInstalled(String packageName,PackageManager packageManager) {
|
||||
try {
|
||||
packageManager.getPackageInfo(packageName, 0);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerScriptResultReceiver(Context context) {
|
||||
// 创建广播接收器
|
||||
scriptResultReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// 获取脚本运行结果(假设结果通过 "result" 键返回)
|
||||
String scriptResult = intent.getStringExtra("result");
|
||||
if (scriptResult != null && !scriptResult.isEmpty()) {
|
||||
// 处理结果并发送给服务端
|
||||
sendResultToServer(scriptResult,context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 注册接收器(假设 Auto.js 广播动作为 "org.autojs.SCRIPT_FINISHED")
|
||||
IntentFilter filter = new IntentFilter("org.autojs.SCRIPT_FINISHED");
|
||||
|
||||
// 使用 ContextCompat.registerReceiver 注册,并设置为 RECEIVER_EXPORTED
|
||||
ContextCompat.registerReceiver(
|
||||
context, // 当前上下文
|
||||
scriptResultReceiver, // 自定义的 BroadcastReceiver
|
||||
filter, // IntentFilter
|
||||
ContextCompat.RECEIVER_EXPORTED // 设置为非导出广播接收器
|
||||
);
|
||||
}
|
||||
|
||||
public static void sendResultToServer(String scriptResult,Context context) {
|
||||
// 使用 Retrofit 或 HttpURLConnection 实现服务端 API 调用
|
||||
Toast.makeText(context, "Sending result to server: " + scriptResult, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// 示例:用 Retrofit 设置服务端请求
|
||||
// 创建 Retrofit 的实例
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://your-server-url.com/") // 替换为服务端 API 地址
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
// 定义 API 接口
|
||||
CloudPhoneManageService api = retrofit.create(CloudPhoneManageService.class);
|
||||
|
||||
// 构建请求体并发送请求
|
||||
Call<Void> call = api.sendScriptResult(new ScriptResultRequest(scriptResult)); // 假设参数是 com.example.studyapp.request.ScriptResultRequest 对象
|
||||
call.enqueue(new Callback<Void>() {
|
||||
@Override
|
||||
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(context, "Result sent successfully", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to send result: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Void> call, Throwable t) {
|
||||
Toast.makeText(context, "Error sending result: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
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;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ChangeDeviceInfo {
|
||||
|
||||
public void changeDeviceInfo(String current_pkg_name,Context context) {
|
||||
// 指定包名优先级高于全局
|
||||
callVCloudSettings_put(current_pkg_name + "_android_id", "my123456",context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen_brightness", "100",context);
|
||||
callVCloudSettings_put(current_pkg_name + "_adb_enabled", "1",context);
|
||||
callVCloudSettings_put(current_pkg_name + "_development_settings_enabled", "1",context);
|
||||
|
||||
callVCloudSettings_put("pm_list_features", "my_pm_list_features",context);
|
||||
callVCloudSettings_put("pm_list_libraries", "my_pm_list_libraries",context);
|
||||
callVCloudSettings_put("system.http.agent", "my_system.http.agent",context);
|
||||
callVCloudSettings_put("webkit.http.agent", "my_webkit.http.agent",context);
|
||||
|
||||
callVCloudSettings_put("global_android_id", "123456",context);
|
||||
|
||||
callVCloudSettings_put("anticheck_pkgs", current_pkg_name,context);
|
||||
|
||||
try {
|
||||
|
||||
JSONObject pkg_info_json = new JSONObject();
|
||||
pkg_info_json.put("versionName", "1.0.0");
|
||||
pkg_info_json.put("versionCode", 100);
|
||||
pkg_info_json.put("firstInstallTime", 1);
|
||||
pkg_info_json.put("lastUpdateTime", 1);
|
||||
callVCloudSettings_put("com.fk.tools_pkgInfo", pkg_info_json.toString(),context);
|
||||
|
||||
JSONObject tmp_json = new JSONObject();
|
||||
tmp_json.put("widthPixels", 1080);
|
||||
tmp_json.put("heightPixels", 1920);
|
||||
tmp_json.put("densityDpi", 440);
|
||||
tmp_json.put("xdpi", 160);
|
||||
tmp_json.put("ydpi", 160);
|
||||
tmp_json.put("density", 3.0);
|
||||
tmp_json.put("scaledDensity", 3.0);
|
||||
callVCloudSettings_put("screen.getDisplayMetrics", tmp_json.toString(),context);
|
||||
callVCloudSettings_put("screen.getMetrics", tmp_json.toString(),context);
|
||||
callVCloudSettings_put("screen.getRealMetrics", tmp_json.toString(),context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getDisplayMetrics.stack", ".getDeviceInfo",context);
|
||||
String stackInfo = Thread.currentThread().getStackTrace()[2].toString();
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getMetrics.stack", stackInfo, context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getRealMetrics.stack", ".getDeviceInfo",context);
|
||||
|
||||
|
||||
tmp_json = new JSONObject();
|
||||
tmp_json.put("width", 1080);
|
||||
tmp_json.put("height", 1820);
|
||||
callVCloudSettings_put("screen.getRealSize", tmp_json.toString(),context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getRealSize.stack", ".getDeviceInfo",context);
|
||||
|
||||
|
||||
tmp_json = new JSONObject();
|
||||
tmp_json.put("left", 0);
|
||||
tmp_json.put("top", 0);
|
||||
tmp_json.put("right", 1080);
|
||||
tmp_json.put("bottom", 1920);
|
||||
callVCloudSettings_put("screen.getCurrentBounds", tmp_json.toString(),context);
|
||||
callVCloudSettings_put("screen.getMaximumBounds", tmp_json.toString(),context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getCurrentBounds.stack", ".getDeviceInfo",context);
|
||||
callVCloudSettings_put(current_pkg_name + "_screen.getMaximumBounds.stack", ".getDeviceInfo",context);
|
||||
|
||||
|
||||
} catch (Throwable e) {
|
||||
Log.e("ChangeDeviceInfo", "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");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置机型, 直接设置属性
|
||||
ShellUtils.execRootCmd("setprop ro.product.brand google");
|
||||
ShellUtils.execRootCmd("setprop ro.product.model raven");
|
||||
ShellUtils.execRootCmd("setprop ro.product.manufacturer google");
|
||||
ShellUtils.execRootCmd("setprop ro.product.device raven");
|
||||
ShellUtils.execRootCmd("setprop ro.product.name raven");
|
||||
ShellUtils.execRootCmd("setprop ro.build.version.incremental 9325679");
|
||||
ShellUtils.execRootCmd("setprop ro.build.fingerprint \"google/raven/raven:13/TQ1A.230105.002/9325679:user/release-keys\"");
|
||||
ShellUtils.execRootCmd("setprop ro.board.platform acr980m");
|
||||
|
||||
Native.setBootId("400079ef55a4475558eb60a0544a43d5");
|
||||
|
||||
// 修改drm id
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id 400079ef55a4475558eb60a0544a43d5171258f13fdd48c10026e2847a6fc7a5");
|
||||
|
||||
// 电量模拟需要大于1000
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity 5000");
|
||||
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor my_gl_vendor");
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer my_gl_renderer");
|
||||
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version \"OpenGL ES 3.2\"");
|
||||
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor my_egl_vendor");
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version my_egl_version");
|
||||
|
||||
}
|
||||
|
||||
private void callVCloudSettings_put(String key, String value, Context context) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("Context cannot be null");
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> clazz = Class.forName("android.provider.VCloudSettings$Global");
|
||||
Method putStringMethod = clazz.getDeclaredMethod("putString", ContentResolver.class, String.class, String.class);
|
||||
putStringMethod.setAccessible(true);
|
||||
putStringMethod.invoke(null, context.getContentResolver(), key, value);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException("Class VCloudSettings$Global not found", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method putString not found", e);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException("Failed to invoke putString via reflection", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetChangedDeviceInfo(String current_pkg_name,Context context) {
|
||||
try {
|
||||
Native.setBootId("00000000000000000000000000000000");
|
||||
} catch (Exception e) {
|
||||
Log.e("resetChangedDeviceInfo", "Failed to set boot ID", e);
|
||||
}
|
||||
|
||||
if (!ShellUtils.hasRootAccess()) {
|
||||
Log.e("resetChangedDeviceInfo", "Root privileges are required.");
|
||||
return;
|
||||
}
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global global_android_id");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_features");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_libraries");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global anticheck_pkgs");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_android_id");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_adb_enabled");
|
||||
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_development_settings_enabled");
|
||||
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id \"\"");
|
||||
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor \"\"");
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer \"\"");
|
||||
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version \"\"");
|
||||
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor \"\"");
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version \"\"");
|
||||
|
||||
ShellUtils.execRootCmd("setprop ro.product.brand Vortex");
|
||||
ShellUtils.execRootCmd("setprop ro.product.model HD65_Select");
|
||||
ShellUtils.execRootCmd("setprop ro.product.manufacturer Vortex");
|
||||
ShellUtils.execRootCmd("setprop ro.product.device HD65_Select");
|
||||
ShellUtils.execRootCmd("setprop ro.product.name HD65_Select");
|
||||
ShellUtils.execRootCmd("setprop ro.build.version.incremental 20240306");
|
||||
ShellUtils.execRootCmd("setprop ro.build.fingerprint \"Vortex/HD65_Select/HD65_Select:13/TP1A.220624.014/20240306:user/release-keys\"");
|
||||
ShellUtils.execRootCmd("setprop ro.board.platform sm8150p");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.example.studyapp.device;
|
||||
|
||||
public class Native {
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("native");
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
e.printStackTrace();
|
||||
// 添加日志或通知以便诊断问题
|
||||
}
|
||||
}
|
||||
|
||||
public native static long[] getSysInfo();
|
||||
|
||||
public native static void getPower();
|
||||
|
||||
public native static void enableBypassAntiDebug(int uid);
|
||||
|
||||
public native static void setBootId(String bootIdHexStr);
|
||||
|
||||
public native static void deviceInfoShow();
|
||||
public native static void deviceInfoChange();
|
||||
|
||||
public native static void deviceInfoReset();
|
||||
|
||||
public native static boolean cpuInfoChange(String path);
|
||||
public native static boolean changeMemSize(String path);
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package com.example.studyapp.proxy;
|
||||
|
||||
import static com.example.studyapp.utils.V2rayUtil.isV2rayRunning;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -26,10 +29,12 @@ public class CustomVpnService extends VpnService {
|
|||
private static final String TUN_ADDRESS = ConfigLoader.getTunnelAddress(); // TUN 的 IP 地址
|
||||
private static final int PREFIX_LENGTH = 28; // 子网掩码
|
||||
|
||||
private Thread vpnTrafficThread; // 保存线程引用
|
||||
private ParcelFileDescriptor vpnInterface; // TUN 接口描述符
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
isVpnActive = true; // 服务启动时激活
|
||||
try {
|
||||
// 检查 V2ray 是否已启动,避免重复进程
|
||||
if (!isV2rayRunning()) {
|
||||
|
@ -46,34 +51,6 @@ public class CustomVpnService extends VpnService {
|
|||
return START_STICKY;
|
||||
}
|
||||
|
||||
private boolean isV2rayRunning() {
|
||||
try {
|
||||
// 执行系统命令,获取当前所有正在运行的进程
|
||||
Process process = Runtime.getRuntime().exec("ps");
|
||||
|
||||
// 读取进程的输出
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 检查是否有包含 "v2ray" 的进程
|
||||
if (line.contains("v2ray")) {
|
||||
Log.i("CustomVpnService", "V2Ray process found: " + line);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查完成,没有找到 "v2ray" 相关的进程
|
||||
Log.i("CustomVpnService", "No V2Ray process is running.");
|
||||
return false;
|
||||
|
||||
} catch (IOException e) {
|
||||
// 捕获异常并记录日志
|
||||
Log.e("CustomVpnService", "Error checking V2Ray process: " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void startVpn() {
|
||||
try {
|
||||
// 配置虚拟网卡
|
||||
|
@ -161,77 +138,102 @@ public class CustomVpnService extends VpnService {
|
|||
return dnsServers;
|
||||
}
|
||||
|
||||
private static CustomVpnService instance;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
instance = this; // 在创建时将实例存储到静态字段
|
||||
}
|
||||
|
||||
public static CustomVpnService getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
@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();
|
||||
vpnInterface = null;
|
||||
Log.d("CustomVpnService", "VPN interface closed.");
|
||||
} catch (IOException e) {
|
||||
Log.e("CustomVpnService", "Error closing VPN interface", e);
|
||||
Log.e("CustomVpnService", "Error closing VPN interface: " + e.getMessage(), e);
|
||||
}
|
||||
vpnInterface = null; // 避免资源泄露
|
||||
}
|
||||
|
||||
// 停止 V2Ray 服务
|
||||
V2rayUtil.stopV2Ray();
|
||||
Log.i("CustomVpnService", "VPN 服务已销毁");
|
||||
}
|
||||
|
||||
private volatile boolean isVpnActive = false; // 标志位控制数据包处理逻辑
|
||||
|
||||
|
||||
private void handleVpnTraffic(ParcelFileDescriptor vpnInterface) {
|
||||
// 启动处理流量的线程
|
||||
vpnTrafficThread = new Thread(() -> {
|
||||
if (vpnInterface == null || !vpnInterface.getFileDescriptor().valid()) {
|
||||
throw new IllegalArgumentException("ParcelFileDescriptor is invalid!");
|
||||
Log.e("CustomVpnService", "ParcelFileDescriptor is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] packetData = new byte[32767];
|
||||
final int maxRetry = 5; // 定义最大重试次数
|
||||
int retryCount = 0;
|
||||
byte[] packetData = new byte[32767]; // 数据包缓冲区
|
||||
|
||||
FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
|
||||
FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor());
|
||||
// 将流放在 try-with-resources 之外,避免循环中被关闭
|
||||
try (FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
|
||||
FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor())) {
|
||||
|
||||
// 循环处理 VPN 数据包
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
while (!Thread.currentThread().isInterrupted()&&isVpnActive) { // 检查线程是否已中断
|
||||
int length;
|
||||
try {
|
||||
int length = inStream.read(packetData);
|
||||
if (length == -1) {
|
||||
// 读取结束
|
||||
break;
|
||||
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);
|
||||
outStream.write(packetData, 0, length); // 未处理的包写回
|
||||
}
|
||||
}
|
||||
}
|
||||
retryCount = 0; // 成功一次后重置重试次数
|
||||
} catch (IOException e) {
|
||||
retryCount++;
|
||||
Log.e("CustomVpnService", "Error reading packet. Retry attempt " + retryCount, e);
|
||||
if (retryCount >= maxRetry) {
|
||||
Log.e("CustomVpnService", "Max retry reached. Exiting loop.");
|
||||
break;
|
||||
}
|
||||
// 可添加短暂延迟来避免频繁重试
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
Log.e("CustomVpnService", "Thread interrupted during sleep", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最终关闭 ParcelFileDescriptor
|
||||
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;
|
||||
|
@ -283,4 +285,5 @@ public class CustomVpnService extends VpnService {
|
|||
|
||||
// 检查是否是 UDP 的 DNS 端口 (53)
|
||||
return sourcePort == 53 || destPort == 53;
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.example.studyapp.utils;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Contains common code for working with Methods/Constructors, extracted and
|
||||
* refactored from <code>MethodUtils</code> when it was imported from Commons
|
||||
* BeanUtils.
|
||||
*
|
||||
* @since 2.5
|
||||
* @version $Id: MemberUtils.java 1143537 2011-07-06 19:30:22Z joehni $
|
||||
*/
|
||||
public abstract class MemberUtils {
|
||||
// TODO extract an interface to implement compareParameterSets(...)?
|
||||
|
||||
private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
|
||||
|
||||
/** Array of primitive number types ordered by "promotability" */
|
||||
private static final Class<?>[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE };
|
||||
|
||||
/**
|
||||
* XXX Default access superclass workaround
|
||||
*
|
||||
* When a public class has a default access superclass with public members,
|
||||
* these members are accessible. Calling them from compiled code works fine.
|
||||
* Unfortunately, on some JVMs, using reflection to invoke these members
|
||||
* seems to (wrongly) prevent access even when the modifier is public.
|
||||
* Calling setAccessible(true) solves the problem but will only work from
|
||||
* sufficiently privileged code. Better workarounds would be gratefully
|
||||
* accepted.
|
||||
*
|
||||
* @param o
|
||||
* the AccessibleObject to set as accessible
|
||||
*/
|
||||
static void setAccessibleWorkaround(AccessibleObject o) {
|
||||
if (o == null || o.isAccessible()) {
|
||||
return;
|
||||
}
|
||||
Member m = (Member) o;
|
||||
if (Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) {
|
||||
try {
|
||||
o.setAccessible(true);
|
||||
} catch (SecurityException e) { // NOPMD
|
||||
// ignore in favor of subsequent IllegalAccessException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given set of modifiers implies package access.
|
||||
*
|
||||
* @param modifiers
|
||||
* to test
|
||||
* @return true unless package/protected/private modifier detected
|
||||
*/
|
||||
static boolean isPackageAccess(int modifiers) {
|
||||
return (modifiers & ACCESS_TEST) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a Member is accessible.
|
||||
*
|
||||
* @param m
|
||||
* Member to check
|
||||
* @return true if <code>m</code> is accessible
|
||||
*/
|
||||
static boolean isAccessible(Member m) {
|
||||
return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the relative fitness of two sets of parameter types in terms of
|
||||
* matching a third set of runtime parameter types, such that a list ordered
|
||||
* by the results of the comparison would return the best match first
|
||||
* (least).
|
||||
*
|
||||
* @param left
|
||||
* the "left" parameter set
|
||||
* @param right
|
||||
* the "right" parameter set
|
||||
* @param actual
|
||||
* the runtime parameter types to match against <code>left</code>
|
||||
* /<code>right</code>
|
||||
* @return int consistent with <code>compare</code> semantics
|
||||
*/
|
||||
public static int compareParameterTypes(Class<?>[] left, Class<?>[] right, Class<?>[] actual) {
|
||||
float leftCost = getTotalTransformationCost(actual, left);
|
||||
float rightCost = getTotalTransformationCost(actual, right);
|
||||
return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of the object transformation cost for each class in the
|
||||
* source argument list.
|
||||
*
|
||||
* @param srcArgs
|
||||
* The source arguments
|
||||
* @param destArgs
|
||||
* The destination arguments
|
||||
* @return The total transformation cost
|
||||
*/
|
||||
private static float getTotalTransformationCost(Class<?>[] srcArgs, Class<?>[] destArgs) {
|
||||
float totalCost = 0.0f;
|
||||
for (int i = 0; i < srcArgs.length; i++) {
|
||||
Class<?> srcClass, destClass;
|
||||
srcClass = srcArgs[i];
|
||||
destClass = destArgs[i];
|
||||
totalCost += getObjectTransformationCost(srcClass, destClass);
|
||||
}
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required needed to turn the source class into
|
||||
* the destination class. This represents the number of steps in the object
|
||||
* hierarchy graph.
|
||||
*
|
||||
* @param srcClass
|
||||
* The source class
|
||||
* @param destClass
|
||||
* The destination class
|
||||
* @return The cost of transforming an object
|
||||
*/
|
||||
private static float getObjectTransformationCost(Class<?> srcClass, Class<?> destClass) {
|
||||
if (destClass.isPrimitive()) {
|
||||
return getPrimitivePromotionCost(srcClass, destClass);
|
||||
}
|
||||
float cost = 0.0f;
|
||||
while (srcClass != null && !destClass.equals(srcClass)) {
|
||||
if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass, true)) {
|
||||
// slight penalty for interface match.
|
||||
// we still want an exact match to override an interface match,
|
||||
// but
|
||||
// an interface match should override anything where we have to
|
||||
// get a superclass.
|
||||
cost += 0.25f;
|
||||
break;
|
||||
}
|
||||
cost++;
|
||||
srcClass = srcClass.getSuperclass();
|
||||
}
|
||||
/*
|
||||
* If the destination class is null, we've travelled all the way up to
|
||||
* an Object match. We'll penalize this by adding 1.5 to the cost.
|
||||
*/
|
||||
if (srcClass == null) {
|
||||
cost += 1.5f;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of steps required to promote a primitive number to
|
||||
* another type.
|
||||
*
|
||||
* @param srcClass
|
||||
* the (primitive) source class
|
||||
* @param destClass
|
||||
* the (primitive) destination class
|
||||
* @return The cost of promoting the primitive
|
||||
*/
|
||||
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
|
||||
float cost = 0.0f;
|
||||
Class<?> cls = srcClass;
|
||||
if (!cls.isPrimitive()) {
|
||||
// slight unwrapping penalty
|
||||
cost += 0.1f;
|
||||
cls = ClassUtils.wrapperToPrimitive(cls);
|
||||
}
|
||||
for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
|
||||
if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
|
||||
cost += 0.1f;
|
||||
if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
|
||||
cls = ORDERED_PRIMITIVE_TYPES[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,9 @@
|
|||
package com.example.studyapp.utils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -10,6 +13,7 @@ import java.io.OutputStream;
|
|||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ShellUtils {
|
||||
|
||||
|
@ -36,21 +40,39 @@ public class ShellUtils {
|
|||
}
|
||||
|
||||
public static boolean hasBin(String binName) {
|
||||
if (!binName.matches("^[a-zA-Z0-9._-]+$")) {
|
||||
throw new IllegalArgumentException("Invalid bin name");
|
||||
// 验证 binName 是否符合规则
|
||||
if (binName == null || binName.isEmpty()) {
|
||||
throw new IllegalArgumentException("Bin name cannot be null or empty");
|
||||
}
|
||||
|
||||
String[] paths = System.getenv("PATH").split(":");
|
||||
for (char c : binName.toCharArray()) {
|
||||
if (!Character.isLetterOrDigit(c) && c != '.' && c != '_' && c != '-') {
|
||||
throw new IllegalArgumentException("Invalid bin name");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 PATH 环境变量
|
||||
String pathEnv = System.getenv("PATH");
|
||||
if (pathEnv == null) {
|
||||
Log.e("hasBin", "PATH environment variable is not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用适合当前系统的路径分隔符分割路径
|
||||
String[] paths = pathEnv.split(File.pathSeparator);
|
||||
for (String path : paths) {
|
||||
File file = new File(path + File.separator + binName);
|
||||
File file = new File(path, binName); // 使用 File 构造完整路径
|
||||
try {
|
||||
if (file.exists() && file.canExecute()) {
|
||||
// 检查文件是否可执行
|
||||
if (file.canExecute()) {
|
||||
return true;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log.e("hasBin", "Security exception occurred: " + e.getMessage());
|
||||
Log.e("hasBin", "Security exception occurred while checking: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未找到可执行文件,返回 false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -60,12 +82,14 @@ public class ShellUtils {
|
|||
throw new IllegalArgumentException("Unsafe or empty command.");
|
||||
}
|
||||
|
||||
if (!isCommandSafe(cmd)) { // 检查命令的合法性
|
||||
throw new IllegalArgumentException("Detected unsafe command.");
|
||||
}
|
||||
|
||||
Process process = null;
|
||||
OutputStream os = null;
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
// 初始化并选择 shell
|
||||
if (hasBin("su")) {
|
||||
Log.e("ShellUtils", "Attempting to execute command: " + cmd);
|
||||
process = Runtime.getRuntime().exec("su");
|
||||
} else if (hasBin("xu")) {
|
||||
process = Runtime.getRuntime().exec("xu");
|
||||
|
@ -75,54 +99,61 @@ public class ShellUtils {
|
|||
process = Runtime.getRuntime().exec("sh");
|
||||
}
|
||||
|
||||
os = process.getOutputStream();
|
||||
// 使用 try-with-resources 关闭流
|
||||
try (OutputStream os = new BufferedOutputStream(process.getOutputStream());
|
||||
InputStream is = process.getInputStream();
|
||||
InputStream errorStream = process.getErrorStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
||||
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream))) {
|
||||
|
||||
// 异步处理错误流
|
||||
Thread errorThread = new Thread(() -> errorReader.lines().forEach(line -> Log.e("ShellUtils", "Shell Error: " + line)));
|
||||
errorThread.start();
|
||||
|
||||
// 写入命令到 shell
|
||||
os.write((cmd + "\n").getBytes());
|
||||
os.write(("exit\n").getBytes());
|
||||
os.write("exit\n".getBytes());
|
||||
os.flush();
|
||||
|
||||
// Handle error stream on a separate thread
|
||||
InputStream errorStream = process.getErrorStream();
|
||||
new Thread(() -> {
|
||||
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream))) {
|
||||
String errorLine;
|
||||
while ((errorLine = errorReader.readLine()) != null) {
|
||||
Log.e("ShellUtils", "Error: " + errorLine);
|
||||
// 等待 process 执行完成
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
||||
process.destroy();
|
||||
throw new RuntimeException("Shell command execution timeout.");
|
||||
}
|
||||
} else {
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (true) {
|
||||
try {
|
||||
process.exitValue();
|
||||
break;
|
||||
} catch (IllegalThreadStateException e) {
|
||||
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
|
||||
process.destroy();
|
||||
throw new RuntimeException("Shell command execution timeout.");
|
||||
}
|
||||
Thread.sleep(100); // Sleep briefly before re-checking
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
process.waitFor();
|
||||
br = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
// 读取输出流中的结果
|
||||
StringBuilder output = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return output.toString().trim();
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Log.e("ShellUtils", "Command execution failed: " + e.getMessage());
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
return "Error: " + e.getMessage();
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void execRootCmd(String cmd) {
|
||||
|
@ -130,13 +161,46 @@ public class ShellUtils {
|
|||
Log.e("ShellUtils", "Unsafe command, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> cmds = new ArrayList<>();
|
||||
cmds.add(cmd);
|
||||
execRootCmds(cmds);
|
||||
|
||||
try {
|
||||
// 使用同步块确保线程安全
|
||||
synchronized (ShellUtils.class) {
|
||||
List<String> results = execRootCmds(cmds);
|
||||
for (String result : results) {
|
||||
Log.d("ShellUtils", "Command Result: " + result); // 可根据生产环境禁用日志
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ShellUtils", "Error executing root command: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCommandSafe(String cmd) {
|
||||
return cmd.matches("^[a-zA-Z0-9._/:\\- ]+$");
|
||||
// 空和空字符串验证
|
||||
if (cmd == null || cmd.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 仅允许安全字符
|
||||
if (!cmd.matches("^[a-zA-Z0-9._/:\\- ]+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加更多逻辑检查,根据预期命令规则
|
||||
// 例如:禁止多重命令,检查逻辑运算符 "&&" 或 "||"
|
||||
if (cmd.contains("&&") || cmd.contains("||")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查命令长度,避免过长命令用于攻击
|
||||
if (cmd.length() > 500) { // 假定命令应当限制在 500 字符以内
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<String> execRootCmds(List<String> cmds) {
|
||||
|
@ -145,25 +209,63 @@ public class ShellUtils {
|
|||
try {
|
||||
// 初始化 Shell 环境
|
||||
process = hasBin("su") ? Runtime.getRuntime().exec("su") : Runtime.getRuntime().exec("sh");
|
||||
try (OutputStream os = process.getOutputStream();
|
||||
InputStream is = process.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
|
||||
|
||||
// 启动读取线程
|
||||
Process stdProcess = process;
|
||||
Thread stdThread = new Thread(() -> {
|
||||
try (BufferedReader stdReader = new BufferedReader(new InputStreamReader(stdProcess.getInputStream()))) {
|
||||
List<String> localResults = new ArrayList<>();
|
||||
String line;
|
||||
while ((line = stdReader.readLine()) != null) {
|
||||
localResults.add(line);
|
||||
Log.d("ShellUtils", "Stdout: " + line);
|
||||
}
|
||||
synchronized (results) {
|
||||
results.addAll(localResults);
|
||||
}
|
||||
} catch (IOException ioException) {
|
||||
Log.e("ShellUtils", "Error reading stdout", ioException);
|
||||
}
|
||||
});
|
||||
|
||||
Process finalProcess = process;
|
||||
Thread errThread = new Thread(() -> {
|
||||
try (BufferedReader errReader = new BufferedReader(new InputStreamReader(finalProcess.getErrorStream()))) {
|
||||
String line;
|
||||
while ((line = errReader.readLine()) != null) {
|
||||
Log.e("ShellUtils", "Stderr: " + line);
|
||||
}
|
||||
} catch (IOException ioException) {
|
||||
Log.e("ShellUtils", "Error reading stderr", ioException);
|
||||
}
|
||||
});
|
||||
|
||||
// 启动子线程
|
||||
stdThread.start();
|
||||
errThread.start();
|
||||
|
||||
try (OutputStream os = process.getOutputStream()) {
|
||||
for (String cmd : cmds) {
|
||||
Log.d("ShellUtils", "Executing command: " + cmd);
|
||||
if (!isCommandSafe(cmd)) {
|
||||
Log.w("ShellUtils", "Skipping unsafe command: " + cmd);
|
||||
continue;
|
||||
}
|
||||
os.write((cmd + "\n").getBytes());
|
||||
Log.d("ShellUtils", "Executing command: " + cmd);
|
||||
}
|
||||
os.write("exit\n".getBytes());
|
||||
os.flush();
|
||||
}
|
||||
|
||||
// 等待子进程完成
|
||||
process.waitFor();
|
||||
// 获取命令输出结果
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
results.add(line);
|
||||
Log.d("ShellUtils", "Command output: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待子线程完成
|
||||
stdThread.join();
|
||||
errThread.join();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("ShellUtils", "Error executing commands: " + e.getMessage());
|
||||
Log.e("ShellUtils", "Error executing commands", e);
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
|
@ -171,4 +273,30 @@ public class ShellUtils {
|
|||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static boolean hasRootAccess() {
|
||||
// 记录是否出现安全异常
|
||||
boolean hasSecurityError = false;
|
||||
|
||||
// 检查二进制文件
|
||||
String[] binariesToCheck = {"su", "xu", "vu"};
|
||||
for (String bin : binariesToCheck) {
|
||||
try {
|
||||
if (hasBin(bin)) {
|
||||
return true;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
hasSecurityError = true;
|
||||
Log.e("ShellUtils", "Security exception while checking: " + bin, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断如果发生安全异常则反馈问题
|
||||
if (hasSecurityError) {
|
||||
Log.w("ShellUtils", "Potential security error detected while checking root access.");
|
||||
}
|
||||
|
||||
// 没有找到合法的二进制文件,则认为无root权限
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ 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;
|
||||
|
@ -108,4 +110,67 @@ public class V2rayUtil {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -3,19 +3,46 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center"
|
||||
tools:context=".MainActivity"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
android:background="@drawable/background">
|
||||
android:background="@drawable/background"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/run_script_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="点击开始运行"
|
||||
android:contentDescription="运行脚本按钮"
|
||||
android:layout_centerInParent="true"/>
|
||||
android:text="开始运行脚本"
|
||||
android:contentDescription="运行脚本按钮" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/connectVpnButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开启vpn"
|
||||
android:contentDescription="开启 VPN 按钮" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/disconnectVpnButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="断开vpn"
|
||||
android:contentDescription="断开 VPN 按钮" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/modifyDeviceInfoButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="修改设备信息"
|
||||
android:contentDescription="修改设备信息按钮" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/resetDeviceInfoButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="重置设备信息"
|
||||
android:contentDescription="重置设备信息按钮" />
|
||||
</LinearLayout>
|
151
err.log
151
err.log
|
@ -1,45 +1,106 @@
|
|||
2025-05-27 19:04:08.548 301-8923 Vpn system_server I Established by com.example.studyapp on tun0
|
||||
2025-05-27 19:04:08.551 41957-42156 CustomVpnService com.example.studyapp D Packet Info: TCP=false, DNS=false, Length=76
|
||||
2025-05-27 19:04:08.553 933-933 GoogleInpu...hodService com...gle.android.inputmethod.latin I GoogleInputMethodService.onStartInput():1247 onStartInput(EditorInfo{EditorInfo{packageName=com.example.studyapp, inputType=0, inputTypeString=NULL, enableLearning=false, autoCorrection=false, autoComplete=false, imeOptions=0, privateImeOptions=null, actionName=UNSPECIFIED, actionLabel=null, initialSelStart=-1, initialSelEnd=-1, initialCapsMode=0, label=null, fieldId=-1, fieldName=null, extras=null, hintText=null, hintLocales=[]}}, false)
|
||||
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 1 (Ask Gemini)
|
||||
java.io.IOException: Stream Closed
|
||||
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||
at java.lang.Thread.run(Thread.java:1012)
|
||||
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 2 (Ask Gemini)
|
||||
java.io.IOException: Stream Closed
|
||||
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||
at java.lang.Thread.run(Thread.java:1012)
|
||||
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 3 (Ask Gemini)
|
||||
java.io.IOException: Stream Closed
|
||||
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||
at java.lang.Thread.run(Thread.java:1012)
|
||||
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 4 (Ask Gemini)
|
||||
java.io.IOException: Stream Closed
|
||||
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||
at java.lang.Thread.run(Thread.java:1012)
|
||||
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 5 (Ask Gemini)
|
||||
java.io.IOException: Stream Closed
|
||||
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||
at java.lang.Thread.run(Thread.java:1012)
|
||||
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Max retry reached. Exiting loop.
|
||||
2025-05-27 19:04:08.582 301-1568 WindowManager system_server V getPackagePerformanceMode -- ComponentInfo{com.example.studyapp/com.example.studyapp.MainActivity} -- com.example.studyapp -- mode=0
|
||||
2025-05-29 15:19:05.558 65246-65246 AndroidRuntime com.example.studyapp D Shutting down VM
|
||||
2025-05-29 15:19:05.559 65246-65246 AndroidRuntime com.example.studyapp E FATAL EXCEPTION: main (Ask Gemini)
|
||||
Process: com.example.studyapp, PID: 65246
|
||||
java.lang.IllegalArgumentException: Service not registered: com.example.studyapp.MainActivity$1@992d8bc
|
||||
at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:2015)
|
||||
at android.app.ContextImpl.unbindService(ContextImpl.java:2076)
|
||||
at android.content.ContextWrapper.unbindService(ContextWrapper.java:889)
|
||||
at android.content.ContextWrapper.unbindService(ContextWrapper.java:889)
|
||||
at com.example.studyapp.MainActivity.stopProxy(MainActivity.java:186)
|
||||
at com.example.studyapp.MainActivity.lambda$onCreate$2$com-example-studyapp-MainActivity(MainActivity.java:94)
|
||||
at com.example.studyapp.MainActivity$$ExternalSyntheticLambda5.onClick(D8$$SyntheticClass:0)
|
||||
at android.view.View.performClick(View.java:7542)
|
||||
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1218)
|
||||
at android.view.View.performClickInternal(View.java:7519)
|
||||
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
|
||||
at android.view.View$PerformClick.run(View.java:29476)
|
||||
at android.os.Handler.handleCallback(Handler.java:942)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:99)
|
||||
at android.os.Looper.loopOnce(Looper.java:201)
|
||||
at android.os.Looper.loop(Looper.java:288)
|
||||
at android.app.ActivityThread.main(ActivityThread.java:7930)
|
||||
at java.lang.reflect.Method.invoke(Native Method)
|
||||
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
|
||||
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
|
||||
2025-05-29 15:19:05.560 301-56098 ActivityTaskManager system_server W Force finishing activity com.example.studyapp/.MainActivity
|
||||
2025-05-29 15:19:05.566 65246-65246 Process com.example.studyapp I Sending signal. PID: 65246 SIG: 9
|
||||
---------------------------- PROCESS ENDED (65246) for package com.example.studyapp ----------------------------
|
||||
2025-05-29 15:19:05.600 301-317 ActivityManager system_server W Exception when unbinding service com.example.studyapp/.proxy.CustomVpnService (Ask Gemini)
|
||||
android.os.DeadObjectException
|
||||
at android.os.BinderProxy.transactNative(Native Method)
|
||||
at android.os.BinderProxy.transact(BinderProxy.java:584)
|
||||
at android.app.IApplicationThread$Stub$Proxy.scheduleUnbindService(IApplicationThread.java:1346)
|
||||
at com.android.server.am.ActiveServices.removeConnectionLocked(ActiveServices.java:4923)
|
||||
at com.android.server.am.ActiveServices.unbindServiceLocked(ActiveServices.java:3173)
|
||||
at com.android.server.am.ActivityManagerService.unbindService(ActivityManagerService.java:12798)
|
||||
at android.app.ContextImpl.unbindService(ContextImpl.java:2079)
|
||||
at com.android.server.connectivity.Vpn$1.interfaceRemoved(Vpn.java:2076)
|
||||
at com.android.server.NetworkManagementService.lambda$notifyInterfaceRemoved$3(NetworkManagementService.java:353)
|
||||
at com.android.server.NetworkManagementService$$ExternalSyntheticLambda6.sendCallback(Unknown Source:2)
|
||||
at com.android.server.NetworkManagementService.invokeForAllObservers(NetworkManagementService.java:314)
|
||||
at com.android.server.NetworkManagementService.notifyInterfaceRemoved(NetworkManagementService.java:353)
|
||||
at com.android.server.NetworkManagementService.-$$Nest$mnotifyInterfaceRemoved(Unknown Source:0)
|
||||
at com.android.server.NetworkManagementService$NetdUnsolicitedEventListener.lambda$onInterfaceRemoved$6$com-android-server-NetworkManagementService$NetdUnsolicitedEventListener(NetworkManagementService.java:620)
|
||||
at com.android.server.NetworkManagementService$NetdUnsolicitedEventListener$$ExternalSyntheticLambda0.run(Unknown Source:4)
|
||||
at android.os.Handler.handleCallback(Handler.java:942)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:99)
|
||||
at android.os.Looper.loopOnce(Looper.java:201)
|
||||
at android.os.Looper.loop(Looper.java:288)
|
||||
at android.os.HandlerThread.run(HandlerThread.java:67)
|
||||
at com.android.server.ServiceThread.run(ServiceThread.java:44)
|
||||
2025-05-29 15:19:05.600 301-317 ActivityManager system_server W Bringing down service while still waiting for start foreground: ServiceRecord{7871f79 u0 com.example.studyapp/.proxy.CustomVpnService}
|
||||
2025-05-29 15:19:05.623 301-326 ActivityManager system_server W Failed to update http proxy for: com.example.studyapp
|
||||
2025-05-29 15:19:05.637 301-1242 ActivityManager system_server I app.info.packageName:com.example.studyapp app.setKilled(true)
|
||||
2025-05-29 15:19:05.637 301-1242 ActivityManager system_server I Process com.example.studyapp (pid 65246) has died: fg +50 TOP
|
||||
2025-05-29 15:19:05.637 301-986 WindowManager system_server I WIN DEATH: Window{d550fa0 u0 com.example.studyapp/com.example.studyapp.MainActivity}
|
||||
2025-05-29 15:19:05.637 301-986 InputManager-JNI system_server W Input channel object 'd550fa0 com.example.studyapp/com.example.studyapp.MainActivity (client)' was disposed without first being removed with the input manager!
|
||||
2025-05-29 15:19:05.660 301-321 WindowManager system_server W Failed to deliver inset state change to w=Window{d550fa0 u0 com.example.studyapp/com.example.studyapp.MainActivity EXITING} (Ask Gemini)
|
||||
android.os.DeadObjectException
|
||||
at android.os.BinderProxy.transactNative(Native Method)
|
||||
at android.os.BinderProxy.transact(BinderProxy.java:584)
|
||||
at android.view.IWindow$Stub$Proxy.insetsControlChanged(IWindow.java:473)
|
||||
at com.android.server.wm.WindowState.notifyInsetsControlChanged(WindowState.java:4015)
|
||||
at com.android.server.wm.InsetsStateController.lambda$notifyPendingInsetsControlChanged$4$com-android-server-wm-InsetsStateController(InsetsStateController.java:351)
|
||||
at com.android.server.wm.InsetsStateController$$ExternalSyntheticLambda2.run(Unknown Source:2)
|
||||
at com.android.server.wm.WindowAnimator.executeAfterPrepareSurfacesRunnables(WindowAnimator.java:345)
|
||||
at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:832)
|
||||
at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:777)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:177)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
|
||||
at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
|
||||
at android.os.Handler.handleCallback(Handler.java:942)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:99)
|
||||
at android.os.Looper.loopOnce(Looper.java:201)
|
||||
at android.os.Looper.loop(Looper.java:288)
|
||||
at android.os.HandlerThread.run(HandlerThread.java:67)
|
||||
at com.android.server.ServiceThread.run(ServiceThread.java:44)
|
||||
2025-05-29 15:19:05.661 301-321 WindowManager system_server W Exception thrown during dispatchAppVisibility Window{d550fa0 u0 com.example.studyapp/com.example.studyapp.MainActivity EXITING} (Ask Gemini)
|
||||
android.os.DeadObjectException
|
||||
at android.os.BinderProxy.transactNative(Native Method)
|
||||
at android.os.BinderProxy.transact(BinderProxy.java:584)
|
||||
at android.view.IWindow$Stub$Proxy.dispatchAppVisibility(IWindow.java:536)
|
||||
at com.android.server.wm.WindowState.sendAppVisibilityToClients(WindowState.java:3478)
|
||||
at com.android.server.wm.WindowContainer.sendAppVisibilityToClients(WindowContainer.java:1234)
|
||||
at com.android.server.wm.WindowToken.setClientVisible(WindowToken.java:392)
|
||||
at com.android.server.wm.ActivityRecord.setClientVisible(ActivityRecord.java:6865)
|
||||
at com.android.server.wm.ActivityRecord.onAnimationFinished(ActivityRecord.java:7687)
|
||||
at com.android.server.wm.ActivityRecord.postApplyAnimation(ActivityRecord.java:5512)
|
||||
at com.android.server.wm.ActivityRecord.commitVisibility(ActivityRecord.java:5472)
|
||||
at com.android.server.wm.ActivityRecord.commitVisibility(ActivityRecord.java:5476)
|
||||
at com.android.server.wm.AppTransitionController.handleClosingApps(AppTransitionController.java:1194)
|
||||
at com.android.server.wm.AppTransitionController.handleAppTransitionReady(AppTransitionController.java:304)
|
||||
at com.android.server.wm.RootWindowContainer.checkAppTransitionReady(RootWindowContainer.java:970)
|
||||
at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:834)
|
||||
at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:777)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:177)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
|
||||
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
|
||||
at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
|
||||
at android.os.Handler.handleCallback(Handler.java:942)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:99)
|
||||
at android.os.Looper.loopOnce(Looper.java:201)
|
||||
at android.os.Looper.loop(Looper.java:288)
|
||||
at android.os.HandlerThread.run(HandlerThread.java:67)
|
||||
at com.android.server.ServiceThread.run(ServiceThread.java:44)
|
||||
2025-05-29 15:19:06.061 301-320 ActivityTaskManager system_server W Activity top resumed state loss timeout for ActivityRecord{e2942a u0 com.example.studyapp/.MainActivity} t-1 f}}
|
||||
|
|
Loading…
Reference in New Issue