应用安装,脚本工具替换

This commit is contained in:
Administrator 2025-07-18 18:26:04 +08:00
parent af757222c1
commit afd85cd10b
13 changed files with 607 additions and 19 deletions

View File

@ -4,14 +4,155 @@
<mappings /> <mappings />
<preferences /> <preferences />
</component> </component>
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="true" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DatabaseAssistantManager"> <component name="DBNavigator.Project.DatabaseAssistantManager">
<assistants /> <assistants>
<assistant-state connection-id="96c06034-88db-4cab-9c16-73c5d99a2c0a" default-profile-name="" selected-profile-name="" selected-model-name="" assistant-type="GENERIC" selected-action="SHOW_SQL" availability="UNAVAILABLE" acknowledgement="NONE">
<messages />
</assistant-state>
</assistants>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseConsoleManager">
<connection id="96c06034-88db-4cab-9c16-73c5d99a2c0a">
<console name="Connection" type="STANDARD" schema="device_info" session="Main" />
</connection>
</component>
<component name="DBNavigator.Project.DatabaseEditorStateManager">
<last-used-providers />
</component> </component>
<component name="DBNavigator.Project.DatabaseFileManager"> <component name="DBNavigator.Project.DatabaseFileManager">
<open-files /> <open-files />
</component> </component>
<component name="DBNavigator.Project.DatabaseSessionManager">
<connection id="96c06034-88db-4cab-9c16-73c5d99a2c0a" />
</component>
<component name="DBNavigator.Project.DatasetFilterManager">
<filter-actions connection-id="96c06034-88db-4cab-9c16-73c5d99a2c0a" dataset="device_info.device_info" active-filter-id="EMPTY_FILTER" />
</component>
<component name="DBNavigator.Project.ExecutionManager">
<retain-sticky-names value="false" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.Settings"> <component name="DBNavigator.Project.Settings">
<connections /> <connections>
<connection id="96c06034-88db-4cab-9c16-73c5d99a2c0a" active="true" signed="true">
<database>
<name value="Connection" />
<description value="" />
<database-type value="SQLITE" />
<config-type value="BASIC" />
<database-version value="3.49" />
<driver-source value="BUNDLED" />
<driver-library value="" />
<driver value="" />
<url-type value="FILE" />
<host value="" />
<port value="" />
<database value="" />
<tns-folder value="" />
<tns-profile value="" />
<files>
<file path="D:\Desktop\device_info.db" schema="device_info" />
</files>
<type value="NONE" />
<user value="" />
<token-type value="" />
<token-config-file value="" />
<token-profile value="" />
<session-user value="" />
</database>
<properties>
<auto-commit value="false" />
</properties>
<ssh-settings>
<active value="false" />
<proxy-host value="" />
<proxy-port value="22" />
<proxy-user value="" />
<auth-type value="PASSWORD" />
<key-file value="" />
</ssh-settings>
<ssl-settings>
<active value="false" />
<certificate-authority-file value="" />
<client-certificate-file value="" />
<client-key-file value="" />
</ssl-settings>
<details>
<charset value="UTF-8" />
<session-management value="true" />
<ddl-file-binding value="true" />
<database-logging value="true" />
<connect-automatically value="true" />
<restore-workspace value="true" />
<restore-workspace-deep value="false" />
<environment-type value="default" />
<connectivity-timeout value="30" />
<idle-time-to-disconnect value="30" />
<idle-time-to-disconnect-pool value="5" />
<credential-expiry-time value="10" />
<max-connection-pool-size value="7" />
<alternative-statement-delimiter value="" />
</details>
<debugger>
<compile-dependencies value="true" />
<tcp-driver-tunneling value="false" />
<tcp-host-address value="" />
<tcp-port-from value="4000" />
<tcp-port-to value="4999" />
<debugger-type value="ASK" />
</debugger>
<object-filters hide-empty-schemas="false" hide-pseudo-columns="false" hide-audit-columns="false">
<object-filters />
<object-type-filter use-master-settings="true">
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="JAVA_CLASS" enabled="true" />
<object-type name="JAVA_INNER_CLASS" enabled="true" />
<object-type name="JAVA_FIELD" enabled="true" />
<object-type name="JAVA_METHOD" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
<object-type name="CREDENTIAL" enabled="true" />
<object-type name="AI_PROFILE" enabled="true" />
</object-type-filter>
</object-filters>
</connection>
</connections>
<browser-settings> <browser-settings>
<general> <general>
<display-mode value="TABBED" /> <display-mode value="TABBED" />
@ -426,4 +567,8 @@
</environment> </environment>
</general-settings> </general-settings>
</component> </component>
<component name="DBNavigator.Project.StatementExecutionManager">
<execution-variables />
<execution-variable-types />
</component>
</project> </project>

View File

@ -18,6 +18,7 @@ import com.android.grape.util.MockTools
import com.android.grape.util.NotificationPermissionHandler import com.android.grape.util.NotificationPermissionHandler
import com.android.grape.util.ScriptUtils.registerScriptResultReceiver import com.android.grape.util.ScriptUtils.registerScriptResultReceiver
import com.android.grape.util.ScriptUtils.unregisterScriptResultReceiver import com.android.grape.util.ScriptUtils.unregisterScriptResultReceiver
import com.android.grape.util.ShellUtil
import com.android.grape.util.StoragePermissionHelper import com.android.grape.util.StoragePermissionHelper
/** /**

View File

@ -75,8 +75,8 @@ data class Device(
var nativeDir: Boolean = false, var nativeDir: Boolean = false,
var network: String = "", var network: String = "",
var noRcLatency: Boolean = false, var noRcLatency: Boolean = false,
@SerializedName("open_referrer") // @SerializedName("open_referrer")//todo string or object
var openReferrer: String = "", // var openReferrer: String = "",
@SerializedName("open_referrerReset") @SerializedName("open_referrerReset")
var openReferrerReset: Int = 0, var openReferrerReset: Int = 0,
var opener: String = "", var opener: String = "",

View File

@ -23,6 +23,7 @@ import com.android.grape.sai.prefers.PreferencesHelper
import com.android.grape.sai.rootless.AndroidPackageInstallerError import com.android.grape.sai.rootless.AndroidPackageInstallerError
import com.android.grape.sai.shell.MiuiUtils import com.android.grape.sai.shell.MiuiUtils
import com.android.grape.sai.shell.Shell import com.android.grape.sai.shell.Shell
import com.android.grape.util.FileUtils
import com.android.grape.util.TaskUtils import com.android.grape.util.TaskUtils
import com.android.grape.util.TaskUtils.setInstallRet import com.android.grape.util.TaskUtils.setInstallRet
import com.blankj.utilcode.util.LogUtils import com.blankj.utilcode.util.LogUtils
@ -137,7 +138,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
} }
androidSessionId = createSession() androidSessionId = createSession()
//todo params.apkSource().apkLocalPath? //todo params.apkSource().apkLocalPath?
val path = "/sdcard/apks/${recordPackageName}" val path = "${FileUtils.CACHE_PATH}${recordPackageName}"
val file = File(path) val file = File(path)
val files = file.listFiles() val files = file.listFiles()

View File

@ -610,7 +610,7 @@ object AppUtils {
) { ) {
true true
} else { } else {
downloadFile(url, "/sdcard/apks/$recordFileName") downloadFile(url, "${FileUtils.CACHE_PATH}$recordFileName")
} }
if (ret) { if (ret) {

View File

@ -127,11 +127,11 @@ object BackupUtils {
*/ */
fun killRecordProcess(context: Context?, packageName: String?) { fun killRecordProcess(context: Context?, packageName: String?) {
Log.i("BackupUtils", "start killRecordProcess :$packageName") Log.i("BackupUtils", "start killRecordProcess :$packageName")
try { try {
val cmd = "am force-stop $packageName" val cmd = "am force-stop $packageName"
Log.i("BackupUtils", "killRecordProcess-> cmd:$cmd") Log.i("BackupUtils", "killRecordProcess-> cmd:$cmd")
MockTools.exec(cmd) ShellUtil.execRootCmdAndGetResult(cmd)
// MockTools.exec(cmd)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

View File

@ -71,11 +71,56 @@ object DeviceUtils {
* @param context * @param context
* @return * @return
*/ */
fun getUserAndGroupSh(context: Context, packageName: String?, string: String): String { fun getUserAndGroupSh(context: Context, packageName: String?, txtFileName: String): String {
return getUserAndGroupSh( try {
context, // String txtFileName = getRecordTxtFileName(context);
recordPackageName, getRecordTxtFileName(context)
) val cmd = "ls /data/data -l | grep $packageName"
val result = MockTools.execRead(cmd)
val txtFile = File(txtFileName)
if (result.length > 20) {
val contents = result
Log.i(
TAG,
"getUserAndGroupSh file->$txtFileName; contents:$contents"
)
if (contents.length > 0) {
val arr =
contents.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var userAndGroup = ""
for (i in arr.indices) {
val line = arr[i]
Log.i(
TAG,
"getUserAndGroup: line=$line"
)
if (line.endsWith(" $packageName")) {
val arr1 = line.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
for (s1 in arr1) {
if (s1.startsWith("u0_")) {
val user = s1
userAndGroup = "$user:$user"
Log.i(
TAG,
"getUserAndGroupSh userAndGroup:$userAndGroup"
)
break
}
}
}
}
return userAndGroup
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
} }
/** /**

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.os.Environment import android.os.Environment
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.android.grape.MainApplication
import com.android.grape.data.AppState.apkDir import com.android.grape.data.AppState.apkDir
import com.android.grape.data.AppState.monitorDir import com.android.grape.data.AppState.monitorDir
import com.android.grape.data.AppState.recordFileName import com.android.grape.data.AppState.recordFileName
@ -43,6 +44,7 @@ object FileUtils {
public const val BUFFER_SIZE = 1024 * 1024 //1M Byte public const val BUFFER_SIZE = 1024 * 1024 //1M Byte
public var name: String? = null public var name: String? = null
val CACHE_PATH = "${MainApplication.instance.cacheDir.absolutePath}/"
fun delFiles(context: Context?) { fun delFiles(context: Context?) {
Log.i("TaskUtils", "start to delFiles : " + startInstallTime) Log.i("TaskUtils", "start to delFiles : " + startInstallTime)
@ -68,7 +70,7 @@ object FileUtils {
} }
public fun getRecordSdcardApkVerFileName(context: Context): String { public fun getRecordSdcardApkVerFileName(context: Context): String {
return "/sdcard/apks/" + recordFileName return "${FileUtils.CACHE_PATH}" + recordFileName
} }
public fun forceCreteDir(file: File) { public fun forceCreteDir(file: File) {

View File

@ -12,7 +12,7 @@ object MockTools {
fun exec(cmd: String): String { fun exec(cmd: String): String {
var retString = "" var retString = ""
try { try {
retString = ShellUtils.execRootCmdAndGetResult(cmd) retString = ShellUtil.execRootCmdAndGetResult(cmd)
// //创建socket // //创建socket
// val myCmd = "SU|$cmd" // val myCmd = "SU|$cmd"
// //
@ -41,7 +41,7 @@ object MockTools {
fun execRead(cmd: String): String { fun execRead(cmd: String): String {
var retString = "" var retString = ""
try { try {
retString = ShellUtils.execRootCmdAndGetResult(cmd) retString = ShellUtil.execRootCmdAndGetResult(cmd)
// //创建socket // //创建socket
// val myCmd = "SU_1|$cmd" // val myCmd = "SU_1|$cmd"
// //

View File

@ -18,6 +18,7 @@ import com.android.grape.net.AfClient.downloadFile
import com.android.grape.receiver.ScriptReceiver import com.android.grape.receiver.ScriptReceiver
import com.android.grape.util.ShellUtils.delFileSh import com.android.grape.util.ShellUtils.delFileSh
import com.android.grape.util.ShellUtils.unzipAPkSh import com.android.grape.util.ShellUtils.unzipAPkSh
import com.android.grape.util.ShellUtils.unzipScriptSh
import com.blankj.utilcode.util.LogUtils import com.blankj.utilcode.util.LogUtils
/** /**
@ -52,7 +53,7 @@ object ScriptUtils {
Log.i("TaskUtils", "execDownScript isDownload : $isDownload") Log.i("TaskUtils", "execDownScript isDownload : $isDownload")
return false return false
} }
unzipAPkSh(script_path, "/sdcard/script/") unzipScriptSh(script_path, "/sdcard/script/")
delFileSh(script_path) delFileSh(script_path)
} }
return isDownload return isDownload

View File

@ -0,0 +1,380 @@
package com.android.grape.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import com.blankj.utilcode.util.LogUtils;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ShellUtil {
public static String getPackagePath(Context context, String packageName) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
return appInfo.sourceDir; // 返回 APK 的完整路径
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "";
}
}
public static void exec(String cmd) {
try {
LogUtils.d(Log.INFO, "ShellUtils", "Executing command: " + cmd, null);
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
} catch (Exception e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Error executing command: " + e.getMessage(), 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) {
// 验证 binName 是否符合规则
if (binName == null || binName.isEmpty()) {
LogUtils.d(Log.ERROR, "ShellUtils", "Invalid bin name",null);
throw new IllegalArgumentException("Bin name cannot be null or empty");
}
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) {
LogUtils.d(Log.ERROR, "ShellUtils", "PATH environment variable is not available", null);
return false;
}
// 使用适合当前系统的路径分隔符分割路径
String[] paths = pathEnv.split(File.pathSeparator);
for (String path : paths) {
File file = new File(path, binName); // 使用 File 构造完整路径
try {
// 检查文件是否可执行
if (file.canExecute()) {
return true;
}
} catch (SecurityException e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Security exception occurred while checking: " + file.getAbsolutePath(), e);
}
}
// 如果未找到可执行文件返回 false
return false;
}
public static String execRootCmdAndGetResult(String cmd) {
Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd);
if (cmd == null || cmd.trim().isEmpty()) {
LogUtils.d(Log.ERROR, "ShellUtils", "Unsafe or empty command. Aborting execution.", null);
throw new IllegalArgumentException("Unsafe or empty command.");
}
// if (!isCommandSafe(cmd)) { // 检查命令的合法性
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
// throw new IllegalArgumentException("Detected unsafe command.");
// }
Process process = null;
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Log.d("ShellUtils", "Determining appropriate shell for execution...");
// if (hasBin("su")) {
// Log.d("ShellUtils", "'su' binary found, using 'su' shell.");
// process = Runtime.getRuntime().exec("su");
// } else if (hasBin("xu")) {
// Log.d("ShellUtils", "'xu' binary found, using 'xu' shell.");
// process = Runtime.getRuntime().exec("xu");
// } else if (hasBin("vu")) {
// Log.d("ShellUtils", "'vu' binary found, using 'vu' shell.");
// process = Runtime.getRuntime().exec("vu");
// } else {
// Log.d("ShellUtils", "No specific binary found, using 'sh' shell.");
// process = Runtime.getRuntime().exec("sh");
// }
process = Runtime.getRuntime().exec("vu");
try (OutputStream os = new BufferedOutputStream(process.getOutputStream());
InputStream is = process.getInputStream();
InputStream errorStream = process.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) {
Log.d("ShellUtils", "Starting separate thread to process error stream...");
executor.submit(() -> {
String line;
try {
while ((line = errorReader.readLine()) != null) {
LogUtils.d(Log.ERROR, "ShellUtils", "Shell Error: " + line, null);
}
} catch (IOException e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Error while reading process error stream: " + e.getMessage(), e);
}
});
Log.d("ShellUtils", "Writing the command to the shell...");
os.write((cmd + "\n").getBytes());
os.write("exit\n".getBytes());
os.flush();
Log.d("ShellUtils", "Command written to shell. Waiting for process to complete.");
StringBuilder output = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
Log.d("ShellUtils", "Shell Output: " + line);
output.append(line).append("\n");
}
Log.d("ShellUtils", "Awaiting process termination...");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!process.waitFor(10, TimeUnit.SECONDS)) {
LogUtils.d(Log.ERROR, "ShellUtils", "Process execution timed out. Destroying process.", null);
process.destroyForcibly();
throw new RuntimeException("Shell command execution timeout.");
}
} else {
Log.d("ShellUtils", "Using manual time tracking method for process termination (API < 26).");
long startTime = System.currentTimeMillis();
while (true) {
try {
process.exitValue();
break;
} catch (IllegalThreadStateException e) {
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
LogUtils.d(Log.ERROR, "ShellUtils", "Process execution timed out (manual tracking). Destroying process.", null);
process.destroy();
throw new RuntimeException("Shell command execution timeout.");
}
Thread.sleep(100); // Sleep briefly before re-checking
}
}
}
Log.d("ShellUtils", "Process terminated successfully. Returning result.");
return output.toString().trim();
}
} catch (IOException | InterruptedException e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Command execution failed: " + e.getMessage(), e);
Thread.currentThread().interrupt();
return "Error: " + e.getMessage();
} finally {
if (process != null) {
Log.d("ShellUtils", "Finalizing process. Attempting to destroy it.");
process.destroy();
}
executor.shutdown();
Log.d("ShellUtils", "Executor service shut down.");
}
}
public static void execRootCmd(String cmd) {
// 校验命令是否安全
if (!isCommandSafe(cmd)) {
LogUtils.d(Log.ERROR, "ShellUtils", "Unsafe command, aborting.", null);
return;
}
List<String> cmds = new ArrayList<>();
cmds.add(cmd);
// 使用同步锁保护线程安全
synchronized (ShellUtils.class) {
try {
List<String> results = execRootCmds(cmds);
// 判断是否需要打印输出仅用于开发调试阶段
for (String result : results) {
Log.d("ShellUtils", "Command Result: " + result);
}
} catch (Exception e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Unexpected error: " + e.getMessage(), e);
}
}
}
private static boolean isCommandSafe(String cmd) {
// 检查空值和空字符串
if (cmd == null || cmd.trim().isEmpty()) {
LogUtils.d(Log.ERROR, "ShellUtils", "Rejected command: empty or null value.", null);
return false;
}
// 检查非法字符
if (!cmd.matches("^[a-zA-Z0-9._/:\\-~`'\" *|]+$")) {
LogUtils.d(Log.ERROR, "ShellUtils", "Rejected command due to illegal characters: " + cmd, null);
return false;
}
// 检查多命令逻辑运算符限制
if (cmd.contains("&&") || cmd.contains("||")) {
Log.d("ShellUtils", "Command contains logical operators.");
if (!isExpectedMultiCommand(cmd)) {
LogUtils.d(Log.ERROR, "ShellUtils", "Rejected command due to prohibited structure: " + cmd, null);
return false;
}
}
// 路径遍历保护
if (cmd.contains("../") || cmd.contains("..\\")) {
LogUtils.d(Log.ERROR, "ShellUtils", "Rejected command due to path traversal attempt: " + cmd, null);
return false;
}
// 命令长度限制
if (cmd.startsWith("tar") && cmd.length() > 800) { // 特定命令支持更长长度
LogUtils.d(Log.ERROR, "ShellUtils", "Command rejected due to excessive length.", null);
return false;
} else if (cmd.length() > 500) {
LogUtils.d(Log.ERROR, "ShellUtils", "Command rejected due to excessive length.", null);
return false;
}
Log.d("ShellUtils", "Command passed safety checks: " + cmd);
return true;
}
// 附加方法检查多命令是否符合预期
private static boolean isExpectedMultiCommand(String cmd) {
// 判断是否为允许的命令组合比如 `cd` `tar` 组合命令
return cmd.matches("^cd .+ && (tar|zip|cp).+");
}
public static List<String> execRootCmds(List<String> cmds) {
List<String> results = new ArrayList<>();
Process process = null;
try {
// 初始化 Shell 环境
process = hasBin("su") ? Runtime.getRuntime().exec("su") : Runtime.getRuntime().exec("sh");
// 启动读取线程
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) {
LogUtils.d(Log.ERROR, "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) {
LogUtils.d(Log.ERROR, "ShellUtils", "Stderr: " + line, null);
}
} catch (IOException ioException) {
LogUtils.d(Log.ERROR, "ShellUtils", "Error reading stderr", ioException);
}
});
// 启动子线程
stdThread.start();
errThread.start();
try (OutputStream os = process.getOutputStream()) {
for (String cmd : cmds) {
// 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();
}
try {
// 执行命令等待解决
process.waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断
LogUtils.d(Log.ERROR, "ShellUtils", "Error executing commands", e);
}
// 等待子线程完成
stdThread.join();
errThread.join();
} catch (InterruptedIOException e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Error reading stdout: Interrupted", e);
Thread.currentThread().interrupt(); // 恢复线程的中断状态
} catch (Exception e) {
LogUtils.d(Log.ERROR, "ShellUtils", "Error executing commands", e);
} finally {
if (process != null) {
process.destroy();
}
}
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;
LogUtils.d(Log.ERROR, "ShellUtils", "Security exception while checking: " + bin, e);
}
}
// 判断如果发生安全异常则反馈问题
if (hasSecurityError) {
Log.w("ShellUtils", "Potential security error detected while checking root access.");
}
// 没有找到合法的二进制文件则认为无root权限
return false;
}
}

View File

@ -558,7 +558,7 @@ object ShellUtils {
return result return result
} }
fun unzipAPkSh(zipFileName: String, dataDir: String) { fun unzipScriptSh(zipFileName: String, dataDir: String) {
try { try {
val file = File(dataDir) val file = File(dataDir)
if (!file.exists()) { if (!file.exists()) {
@ -571,6 +571,19 @@ object ShellUtils {
e.printStackTrace() e.printStackTrace()
} }
} }
fun unzipAPkSh(zipFileName: String, dataDir: String) {
try {
val file = File(dataDir)
if (!file.exists()) {
FileUtils.forceCreteDir(file)
}
val cmd = "unzip -o " + FileUtils.CACHE_PATH + File(zipFileName).name + " -d " + dataDir
val unzipResult = MockTools.execRead(cmd)
Log.i("ShellUtils", "unZipFileSh-> cmd:$unzipResult")
} catch (e: Exception) {
e.printStackTrace()
}
}
/** /**
* 执行shell命令以检索指定文件的内容 * 执行shell命令以检索指定文件的内容

View File

@ -135,7 +135,7 @@ object TaskUtils {
} }
fun isInstallRet(): Boolean { fun isInstallRet(): Boolean {
return installRet ?: false return installRet == true
} }
fun setAfLog(afLogV: String) { fun setAfLog(afLogV: String) {