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() } }