diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..33b91aa 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -5,6 +5,9 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 7b3006b..639c779 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/app/build.gradle b/app/build.gradle
index 4748370..1586aec 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -37,4 +37,13 @@ dependencies {
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
+ // Retrofit 核心库
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+
+ // 如果需要用 Gson 作为 JSON 序列化/反序列化工具,还需添加以下依赖
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+
+ // 如果需要 RxJava 支持(可选)
+ implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 56fd920..c11b271 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,16 @@
+
+
+
+
+
+
+
+
+
+
vpnRequestLauncher;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ vpnRequestLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ startProxyVpnService();
+ } else {
+ Toast.makeText(this, "VPN setup failed", Toast.LENGTH_SHORT).show();
+ new AlertDialog.Builder(this)
+ .setTitle("VPN 配置失败")
+ .setMessage("未能成功配置 VPN。要重试吗?")
+ .setPositiveButton("重试", (dialog, which) -> startProxyVpn(this))
+ .setNegativeButton("取消", null)
+ .show();
+ }
+ }
+ );
+
// 检查存储权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -30,6 +68,8 @@ public class MainActivity extends AppCompatActivity {
REQUEST_CODE_STORAGE_PERMISSION);
}
+ startProxyVpn(this);
+
// 查找按钮对象
Button runScriptButton = findViewById(R.id.run_script_button);
if (runScriptButton != null) {
@@ -39,21 +79,46 @@ public class MainActivity extends AppCompatActivity {
}
}
+ private void startProxyVpn(Context context) { // 避免强制依赖 MainActivity
+ Intent intent = VpnService.prepare(context);
+ if (intent != null) {
+ vpnRequestLauncher.launch(intent);
+ } else {
+ startProxyVpnService();
+ }
+ }
+
+ private void startProxyVpnService() {
+ Intent serviceIntent = new Intent(this, ProxyVpnService.class);
+ startService(serviceIntent);
+ }
+
@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) {
+ permissionHandler();
Toast.makeText(this, "Permissions granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
+ // 可选择终止操作或退出程序
+ finish(); // 假设应用需要此权限才能运行
}
}
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (scriptResultReceiver != null) {
+ unregisterReceiver(scriptResultReceiver);
+ }
+ }
+
private void runAutojsScript() {
// 定义脚本文件路径
- File scriptFile = new File(getExternalFilesDir(null), "脚本/adsense.js");
+ File scriptFile = new File(Environment.getExternalStorageDirectory(), "脚本/chromium.js");
// 检查文件是否存在
if (!scriptFile.exists()) {
@@ -75,6 +140,8 @@ public class MainActivity extends AppCompatActivity {
// 启动 Auto.js
try {
+ // 模拟:通过广播监听脚本运行结果
+ registerScriptResultReceiver(); // 注册结果回调监听(假设脚本通过广播返回结果)
startActivity(intent);
Toast.makeText(this, "Running script: " + scriptFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
@@ -93,4 +160,82 @@ public class MainActivity extends AppCompatActivity {
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 call = api.sendScriptResult(new ScriptResultRequest(scriptResult)); // 假设参数是 com.example.studyapp.request.ScriptResultRequest 对象
+ call.enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response 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 call, Throwable t) {
+ Toast.makeText(MainActivity.this, "Error sending result: " + t.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private void grantPermission(String name)
+ {
+ ShellUtils.execRootCmd("pm grant " + this.getPackageName() + " " + name);
+ }
+ private void appopsAllow(String name)
+ {
+ ShellUtils.execRootCmd("appops set " + this.getPackageName() + " " + name + " allow");
+ }
+ private void permissionHandler() {
+ grantPermission("android.permission.SYSTEM_ALERT_WINDOW");
+ grantPermission("android.permission.FOREGROUND_SERVICE");
+ grantPermission("android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION");
+ grantPermission("android.permission.WRITE_SECURE_SETTINGS");
+ grantPermission("android.permission.READ_EXTERNAL_STORAGE");
+ grantPermission("android.permission.WRITE_EXTERNAL_STORAGE");
+ appopsAllow("MANAGE_EXTERNAL_STORAGE");
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/ProxyVpnService.java b/app/src/main/java/com/example/studyapp/ProxyVpnService.java
new file mode 100644
index 0000000..02b8573
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/ProxyVpnService.java
@@ -0,0 +1,44 @@
+package com.example.studyapp;
+
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+public class ProxyVpnService extends VpnService {
+ private ParcelFileDescriptor vpnInterface;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Builder builder = new Builder();
+ try {
+ builder.addAddress("10.0.2.15", 24); // 配置虛擬 IP
+ builder.addRoute("0.0.0.0", 0); // 配置攔截所有流量的路由
+ builder.setSession("Proxy VPN Service");
+ builder.addDnsServer("8.8.8.8"); // 設置 DNS
+ vpnInterface = builder.establish(); // 啟動 VPN 通道,保存接口描述符
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (vpnInterface != null) {
+ try {
+ vpnInterface.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // 在实际实现中可能需要处理 Intent 数据
+ return START_STICKY;
+ }
+}
diff --git a/app/src/main/java/com/example/studyapp/request/ScriptResultRequest.java b/app/src/main/java/com/example/studyapp/request/ScriptResultRequest.java
new file mode 100644
index 0000000..f98c185
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/request/ScriptResultRequest.java
@@ -0,0 +1,18 @@
+package com.example.studyapp.request;
+
+// 这是发送到服务端的请求体(JSON 格式)
+public class ScriptResultRequest {
+ private String result;
+
+ public ScriptResultRequest(String result) {
+ this.result = result;
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+ public void setResult(String result) {
+ this.result = result;
+ }
+}
diff --git a/app/src/main/java/com/example/studyapp/service/CloudPhoneManageService.java b/app/src/main/java/com/example/studyapp/service/CloudPhoneManageService.java
new file mode 100644
index 0000000..f02a52c
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/service/CloudPhoneManageService.java
@@ -0,0 +1,15 @@
+package com.example.studyapp.service;
+
+import com.example.studyapp.request.ScriptResultRequest;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+
+public interface CloudPhoneManageService {
+
+ // 假设服务端接口接收 POST 请求
+ @POST("/api/script/result")
+ Call sendScriptResult(@Body ScriptResultRequest request);
+
+}
diff --git a/app/src/main/java/com/example/studyapp/utils/DeviceUtils.java b/app/src/main/java/com/example/studyapp/utils/DeviceUtils.java
new file mode 100644
index 0000000..6cc7a80
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/utils/DeviceUtils.java
@@ -0,0 +1,27 @@
+package com.example.studyapp.utils;
+
+public class DeviceUtils {
+ // 加载本地库
+ static {
+ try {
+ System.loadLibrary("native");
+ } catch (UnsatisfiedLinkError e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** 设置设备ID */
+ public static native void setDeviceId(String deviceId);
+
+ /** 更新Boot ID */
+ public static native void updateBootId(String newBootId);
+
+ /** 获取系统信息 */
+ public static native long[] getSysInfo();
+
+ /** 修改CPU信息,需系统权限 */
+ public static native boolean cpuInfoChange(String path);
+
+ /** 修改内存大小,需系统权限 */
+ public static native boolean changeMemSize(String path);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/utils/ShellUtils.java b/app/src/main/java/com/example/studyapp/utils/ShellUtils.java
new file mode 100644
index 0000000..23c6387
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/utils/ShellUtils.java
@@ -0,0 +1,172 @@
+package com.example.studyapp.utils;
+
+import java.io.BufferedReader;
+import android.util.Log;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShellUtils {
+
+ public static void exec(String cmd) {
+ try {
+ Log.e("ShellUtils", String.format("exec %s", cmd));
+ Process process = Runtime.getRuntime().exec(cmd);
+ process.waitFor();
+ } catch (Exception e) {
+ }
+ }
+
+ public static int getPid(Process p) {
+ int pid = -1;
+ try {
+ Field f = p.getClass().getDeclaredField("pid");
+ f.setAccessible(true);
+ pid = f.getInt(p);
+ f.setAccessible(false);
+ } catch (Throwable e) {
+ pid = -1;
+ }
+ return pid;
+ }
+
+ public static boolean hasBin(String binName) {
+ if (!binName.matches("^[a-zA-Z0-9._-]+$")) {
+ throw new IllegalArgumentException("Invalid bin name");
+ }
+
+ String[] paths = System.getenv("PATH").split(":");
+ for (String path : paths) {
+ File file = new File(path + File.separator + binName);
+ try {
+ if (file.exists() && file.canExecute()) {
+ return true;
+ }
+ } catch (SecurityException e) {
+ Log.e("hasBin", "Security exception occurred: " + e.getMessage());
+ }
+ }
+ return false;
+ }
+
+ public static String execRootCmdAndGetResult(String cmd) {
+ if (cmd == null || cmd.trim().isEmpty() || !isCommandSafe(cmd)) {
+ Log.e("ShellUtils", "Unsafe or empty command. Aborting execution.");
+ return null;
+ }
+
+ Process process = null;
+ OutputStream os = null;
+ BufferedReader br = null;
+ try {
+ 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");
+ } else if (hasBin("vu")) {
+ process = Runtime.getRuntime().exec("vu");
+ } else {
+ process = Runtime.getRuntime().exec("sh");
+ }
+
+ os = process.getOutputStream();
+ os.write((cmd + "\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);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }).start();
+
+ process.waitFor();
+ br = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } 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) {
+ if (!isCommandSafe(cmd)) {
+ Log.e("ShellUtils", "Unsafe command, aborting.");
+ return;
+ }
+ List cmds = new ArrayList<>();
+ cmds.add(cmd);
+ execRootCmds(cmds);
+ }
+
+ private static boolean isCommandSafe(String cmd) {
+ return cmd.matches("^[a-zA-Z0-9._/:\\- ]+$");
+ }
+
+ public static void execRootCmds(List cmds) {
+ Process process = null;
+ try {
+ if (hasBin("su")) {
+ process = Runtime.getRuntime().exec("su");
+ } else if (hasBin("xu")) {
+ process = Runtime.getRuntime().exec("xu");
+ } else if (hasBin("vu")) {
+ process = Runtime.getRuntime().exec("vu");
+ } else {
+ process = Runtime.getRuntime().exec("sh");
+ }
+
+ try (OutputStream os = process.getOutputStream()) {
+ for (String cmd : cmds) {
+ Log.e("ShellUtils", "Executing command");
+ os.write((cmd + "\n").getBytes());
+ }
+ os.write("exit\n".getBytes());
+ os.flush();
+ }
+ process.waitFor();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (process != null) {
+ process.destroy();
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/mipmap-hdpi/box.png b/app/src/main/res/mipmap-hdpi/box.png
index 7bb42a8..2e40e33 100644
Binary files a/app/src/main/res/mipmap-hdpi/box.png and b/app/src/main/res/mipmap-hdpi/box.png differ
diff --git a/settings.gradle b/settings.gradle
index 9b4d0b9..cba15fe 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -12,10 +12,11 @@ pluginManagement {
}
}
dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
+ mavenLocal()
}
}