package net.geedge.api.util; import cn.hutool.core.codec.Base32Codec; import cn.hutool.core.thread.NamedThreadFactory; import cn.hutool.log.Log; import net.geedge.api.entity.EnvApiYml; import net.geedge.common.APIException; import net.geedge.common.Constant; import net.geedge.common.RCode; import net.geedge.common.T; import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public class AdbUtil { private static final Log log = Log.get(); private static AdbUtil instance; private static String DEFAULT_DROIDVNC_NG_PKG_NAME = "net.christianbeier.droidvnc_ng"; private static String DEFAULT_DROIDVNC_NG_APK_PATH = "./lib/droidvnc-np-2.6.0.apk"; private static String DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH = "./lib/droidvnc-np-defaults.json"; private static String ANDROID_LAUNCHER = "./lib/android-launcher.py"; private String serial; private String host; private Integer port; private Integer vncPort; private CommandExec commandExec; private boolean interrupt; private ExecutorService threadPool; public String getSerial() { return T.StrUtil.isNotEmpty(this.serial) ? serial : String.format("%s:%s", this.host, this.port); } public void setInterrupt(boolean interrupt) { this.interrupt = interrupt; } public record CommandResult(Integer exitCode, String output) { } public AdbUtil(EnvApiYml.Adb adb, CommandExec commandExec) { this.serial = T.StrUtil.emptyToDefault(adb.getSerial(), ""); this.host = adb.getHost(); this.port = adb.getPort(); this.vncPort = adb.getVncPort(); this.commandExec = commandExec; // adb connect if (!this.connect()) { log.error("[connect error, program exit]"); Runtime.getRuntime().halt(1); } // init this.init(true); } public static AdbUtil getInstance() { if (instance == null) { throw new IllegalArgumentException("Object has not been instantiated."); } return instance; } public static AdbUtil getInstance(EnvApiYml.Adb connInfo, CommandExec commandExec) { if (instance == null) { synchronized (AdbUtil.class) { if (instance == null) { instance = new AdbUtil(connInfo, commandExec); } } } return instance; } /** * connect */ public boolean connect() { if (T.StrUtil.isNotEmpty(this.serial)) { // local AdbDevice adbDevice = this.getAdbDevice(); log.info("[connect] [result: {}]", T.JSONUtil.toJsonStr(adbDevice)); if (null == adbDevice || !adbDevice.isAvailable()) { return false; } } else { // remote String result = commandExec.exec(AdbCommandBuilder.builder() .buildConnectCommand(this.host, this.port) .build()); log.info("[connect] [result: {}]", result); if (!T.StrUtil.contains(result, "connected")) { return false; } } return true; } /** * init * su root * install droidVNC NG */ public void init(boolean install) { // adb root String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildRootCommand() .build() ); log.info("[init] [adb root] [result: {}]", result); if (install) { // install droidVNC NG CommandResult installed = this.install(DEFAULT_DROIDVNC_NG_APK_PATH, true, true); log.info("[init] [install droidVNC NG] [result: {}]", installed); // 上传默认配置 this.execShellCommand("shell mkdir -p /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files"); this.push(DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH, "/storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files/defaults.json"); // 无障碍权限 this.execShellCommand("shell settings put secure enabled_accessibility_services net.christianbeier.droidvnc_ng/.InputService:$(settings get secure enabled_accessibility_services)"); // 存储空间权限 this.execShellCommand("shell pm grant net.christianbeier.droidvnc_ng android.permission.WRITE_EXTERNAL_STORAGE"); // 屏幕录制权限 this.execShellCommand("shell appops set net.christianbeier.droidvnc_ng PROJECT_MEDIA allow"); // ACTION_STOP this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_STOP --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0"); } // ACTION_START this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_START --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0"); // 添加自定义链 this.addAswOutputChain(); } /** * status * * @return */ public Map status() { Map m = T.MapUtil.builder() .put("platform", "android") .build(); AdbDevice device = this.getAdbDevice(); m.put("status", device.isAvailable() ? "online" : "offline"); Map prop = this.getProp(); m.put("name", T.MapUtil.getStr(prop, "ro.product.name", "")); m.put("brand", T.MapUtil.getStr(prop, "ro.product.brand", "")); m.put("model", T.MapUtil.getStr(prop, "ro.product.model", "")); m.put("version", T.MapUtil.getStr(prop, "ro.build.version.release", "")); m.put("resolution", T.MapUtil.getStr(prop, "wm.size", "")); // 默认为真机 String type = "device"; for (Map.Entry entry : prop.entrySet()) { // 根据 ro.build.* 这一组配置值判定是否为模拟器,如果包含 emulator、sdk 则为模拟器 if (entry.getKey().contains("ro.build")) { String value = entry.getValue(); if (T.StrUtil.containsAnyIgnoreCase(value, "emulator", "sdk")) { type = "emulator"; break; } } } m.put("type", type); // check root String checkRootResult = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildCheckRootCommand() .build() ); m.put("root", !T.StrUtil.containsIgnoreCase(checkRootResult, "Permission denied")); return m; } /** * getAdbDevice * adb devices -l * * @return */ private AdbDevice getAdbDevice() { String result = commandExec.exec(AdbCommandBuilder.builder() .buildDevicesCommand() .build() ); List list = T.ListUtil.list(true); String[] lines = result.split("\\n"); for (String line : lines) { if (line.startsWith("*") || line.startsWith("List") || T.StrUtil.isEmpty(line)) continue; list.add(new AdbDevice(line)); } AdbDevice adbDevice = list.stream() .filter(pojo -> T.StrUtil.equals(pojo.getSerial(), this.getSerial())) .findFirst() .orElse(null); return adbDevice; } /** * getProp * adb shell getprop * * @return */ private Map getProp() { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildGetpropCommand() .build() ); Map prop = new LinkedHashMap<>(); Pattern pattern = Pattern.compile("\\[(.*?)\\]: \\[(.*?)\\]"); Matcher matcher = pattern.matcher(result); while (matcher.find()) { String key = matcher.group(1).trim(); String value = matcher.group(2).trim(); prop.put(key, value); } // 分辨率 Physical size: 1440x3040 String wmSize = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildWmSizeCommand() .build() ); prop.put("wm.size", T.StrUtil.trim(wmSize.replaceAll("Physical size: ", ""))); return prop; } /** * md5sum */ private CommandResult md5sum(String path) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildMd5sumCommand(path) .build() ); log.info("[md5sum] [path: {}] [result: {}]", path, result); if (T.StrUtil.isNotEmpty(result)) { String md5 = result.split("\\s+")[0]; return new CommandResult(0, md5); } return new CommandResult(1, ""); } /** * push * 0 success; !0 failed */ public CommandResult push(String local, String remote) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPushCommand(local, remote) .build() ); log.info("[push] [local: {}] [remote: {}] [result: {}]", local, remote, result); return new CommandResult(T.StrUtil.contains(result, "failed") ? 1 : 0, result); } /** * pull * 0 success; !0 failed */ public CommandResult pull(String remote, String local) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPullCommand(remote, local) .build() ); log.info("[pull] [remote: {}] [local: {}] [result: {}]", remote, local, result); return new CommandResult(T.StrUtil.containsAny(result, "file pulled", "files pulled") ? 0 : 1, result); } /** * list dir * ls -l * stat filename */ public List listDir(String path) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildLsDirCommand(path) .build() ); if (T.StrUtil.contains(result, "No such file or directory")) { log.warn("[listDir] [path: {}] [result: {}]", path, result); throw new APIException(RCode.NOT_EXISTS); } if (T.StrUtil.contains(result, "Permission denied")) { log.warn("[listDir] [path: {}] [result: {}]", path, result); throw new APIException(RCode.NOT_PERMISSION); } List> futureList = T.ListUtil.list(false); List listDir = T.ListUtil.list(true); String[] lines = result.split("\\n"); boolean isDir = false; for (String line : lines) { if (line.startsWith("total")) { isDir = true; continue; } String[] split = line.split("\\s+"); String name; // link file|dir if (10 == split.length) { name = split[7]; } else { name = split[split.length - 1]; } String statFilePath = isDir ? Paths.get(path).resolve(name).toString() : path; String statCommand = "shell stat -c \"'%N %A %g %u %s %a %X %Y'\" " + statFilePath; futureList.add( CompletableFuture.supplyAsync(() -> { String statResult = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(statCommand.replaceAll("\\\\", "/")) .build() ); // reverse result List list = Arrays.asList(statResult.split("\\s+")); Collections.reverse(list); String fullName = list.get(7).replaceAll("'|`", ""); Map relMap = T.MapUtil.newHashMap(); relMap.put("name", name); relMap.put("value", T.MapUtil.builder() .put("id", Base32Codec.Base32Encoder.ENCODER.encode(fullName.getBytes())) .put("fullName", fullName) .put("gid", Long.parseLong(list.get(5))) .put("uid", Long.parseLong(list.get(4))) .put("size", Long.parseLong(list.get(3))) .put("permissions", Long.parseLong(list.get(2))) .put("cts", Long.parseLong(list.get(1))) .put("uts", Long.parseLong(list.get(0))) .put("isDir", list.get(6).startsWith("d")) .put("isBlk", list.get(6).startsWith("b")) .put("isFifo", list.get(6).startsWith("p")) .put("isLink", list.get(6).startsWith("l")) .put("isReg", list.get(6).startsWith("-")) .put("isSock", list.get(6).startsWith("s")) .build()); return relMap; }, getThreadPool()) ); Map m = T.MapUtil.builder() .put("name", name) .build(); listDir.add(m); } try { CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(); futureList.forEach(f -> { Map map = f.getNow(null); if (T.MapUtil.isNotEmpty(map)) { String name = T.MapUtil.getStr(map, "name"); Map fileAttr = listDir.stream().filter(m -> T.MapUtil.getStr(m, "name").equals(name)).findFirst().get(); fileAttr.putAll(T.MapUtil.get(map, "value", Map.class)); } }); } catch (Exception e) { log.warn(e); } return listDir; } /** * listApp * adb shell pm list packages * * @return */ public List listApp(String arg) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildPmListPackagesCommand(arg) .build() ); List listApp = T.ListUtil.list(true); List> futureList = T.ListUtil.list(false); String prefix = "package:"; String[] lines = result.split("\\n"); for (String line : lines) { String packageName = T.StrUtil.trim(line.substring(prefix.length())); if (T.StrUtil.equals(DEFAULT_DROIDVNC_NG_PKG_NAME, packageName)) continue; String dumpsysResult = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell dumpsys package " + packageName) .build() ); String[] split = dumpsysResult.split("\\n"); String version = "", apkPath = ""; for (String s : split) { if (s.contains("versionName=")) { version = T.StrUtil.trim(s).replaceAll("versionName=", ""); } if (s.contains("path: ")) { apkPath = T.StrUtil.trim(s).replaceAll("path: ", ""); } } if (T.StrUtil.isNotEmpty(apkPath)) { String finalApkPath = apkPath; futureList.add( CompletableFuture.supplyAsync(() -> { try { CommandResult md5sumRes = this.md5sum(finalApkPath); String md5Value = md5sumRes.output(); File localApk = T.FileUtil.file(Constant.TEMP_PATH, md5Value + ".apk"); if (!T.FileUtil.exist(localApk)) { CommandResult pulled = this.pull(finalApkPath, localApk.getAbsolutePath()); if (0 != pulled.exitCode()) { log.warn("[listApp] [pull apk error] [pkg: {}]", packageName); return null; } } ApkUtil apkUtil = new ApkUtil(); ApkInfo apkInfo = apkUtil.parseApk(localApk.getAbsolutePath()); String appName = apkInfo.getLabel(); // String iconFilename = apkInfo.getIcon(); // String base64IconDate = apkUtil.extractFileFromApk(localApk.getAbsolutePath(), iconFilename); // if (T.StrUtil.isNotEmpty(base64IconDate)) { // base64IconDate = "data:image/jpeg;base64," + base64IconDate; // } Map relMap = T.MapUtil.newHashMap(); relMap.put("pkg", packageName); relMap.put("value", T.MapUtil.builder() .put("name", appName) // .put("icon", base64IconDate) .build()); return relMap; } catch (Exception e) { log.error(e, "[listApp] [parse apk] [pkg: {}]", packageName); } return null; }, getThreadPool()) ); } Map m = T.MapUtil.builder() .put("packageName", packageName) .put("version", version) .build(); listApp.add(m); } try { CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get(); futureList.forEach(f -> { Map map = f.getNow(null); if (T.MapUtil.isNotEmpty(map)) { String pkg = T.MapUtil.getStr(map, "pkg"); Map appAttr = listApp.stream().filter(m -> T.MapUtil.getStr(m, "packageName").equals(pkg)).findFirst().get(); appAttr.putAll(T.MapUtil.get(map, "value", Map.class)); } }); } catch (Exception e) { log.warn(e); } return listApp; } /** * install app * adb install apk */ public CommandResult install(String localFilePath, boolean isDebugApk, boolean isReInstall) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildInstallCommand(localFilePath, isDebugApk, isReInstall) .build()); log.info("[install] [localFilePath: {}] [isDebugApk: {}] [isReInstall: {}] [result: {}]", localFilePath, isDebugApk, isReInstall, result); return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result); } /** * uninstall app * adb uninstall package_name */ public CommandResult uninstall(String packageName) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildUnInstallCommand(packageName) .build() ); log.info("[uninstall] [packageName: {}] [result: {}]", packageName, result); return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result); } /** * stop app * adb shell am force-stop package_name */ public CommandResult stopApp(String packageName) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildStopAppCommand(packageName) .build() ); log.info("[stopApp] [packageName: {}] [result: {}]", packageName, result); return new CommandResult(T.StrUtil.isEmpty(result) ? 0 : 1, result); } /** * iptables -F * iptables -X */ @Deprecated private void cleanIptables() { // Delete all rules in chain or all chains commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -F") .build() ); // Delete user-defined chain commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -X") .build() ); } /** * list tcpdump */ public List listTcpdump() { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep capture_ | awk '{print $NF}' \"")) .build()); List list = T.ListUtil.list(true); String[] lines = result.split("\\n"); for (String line : lines) { try { String fileName = T.FileUtil.mainName(line); String taskId = "", packageName = ""; if (fileName.contains("capture_all_")) { taskId = fileName.replaceAll("capture_all_", ""); } else { String[] split = fileName.split("_"); packageName = split[2]; taskId = split[split.length - 1]; } Map m = T.MapUtil.builder() .put("id", taskId) .put("packageName", packageName) .build(); list.add(m); } catch (Exception e) { log.warn(e, "[listTcpdump] [get task info error] [line: {}]", line); } } return list; } /** * start Tcpdump * tcpdump pcap */ public CommandResult startTcpdump(String packageName) { String taskId = T.IdUtil.fastSimpleUUID(); if (T.StrUtil.isNotEmpty(packageName)) { log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName); String dumpsysResult = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell dumpsys package " + packageName) .build() ); String[] lines = dumpsysResult.split("\\n"); String userId = Arrays.stream(lines) .filter(s -> T.StrUtil.contains(s, "userId=")) .findFirst() .map(s -> T.StrUtil.trim(s).replaceAll("userId=", "")) .orElseThrow(() -> new APIException("Not found userId by package name. package name: " + packageName)); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId)) .build()); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -A OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); String ruleList = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell iptables -L") .build()); log.info("[startTcpdump] [iptables -L] [result: {}]", ruleList); // pcap 格式:capture_{userId}_{pcakageName}_{taskId}.pcap String pcapFilePath = "/data/local/tmp/capture_" + userId + "_" + packageName + "_" + taskId + ".pcap"; commandExec.execForProcess(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath)) .build()); } else { log.info("[startTcpdump] [capture all package]"); // pcap 格式:capture_all_{taskId}.pcap String pcapFilePath = "/data/local/tmp/capture_all_" + taskId + ".pcap"; commandExec.execForProcess(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell tcpdump not port %s -w %s &", this.vncPort, pcapFilePath)) .build()); } String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' \"", taskId)) .build()); log.info("[startTcpdump] [taskId: {}] [tcpdump pid: {}]", taskId, result); return new CommandResult(T.StrUtil.isNotEmpty(result) ? 0 : 1, taskId); } /** * stop tcpdump * kill -INT {pid} */ public CommandResult stopTcpdump(String id) { String pcapFilePath = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $NF}' \"", id)) .build()); if (T.StrUtil.isNotEmpty(pcapFilePath)) { if (!pcapFilePath.contains("capture_all_")) { // 删除 iptables rule String[] split = T.FileUtil.mainName(pcapFilePath).split("_"); String userId = split[1]; log.info("[stopTcpdump] [remove iptables rule] [userId: {}]", userId); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId)) .build()); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell iptables -D OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId)) .build()); } } String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \"", id)) .build()); // 等待 tcpdump 资源释放,避免出现错误:The capture file appears to have been cut short in the middle of a packet. for (int i = 0; i < 10; i++) { T.ThreadUtil.sleep(500); String str = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s \"", id)) .build()); log.info("[stopTcpdump] [id: {}] [is running: {}]", id, str); if (T.StrUtil.isEmpty(str)) { break; } } log.info("[stopTcpdump] [id: {}] [pcapFilePath: {}] [result: {}]", id, pcapFilePath, result); if (T.StrUtil.isEmpty(result)) { return new CommandResult(0, pcapFilePath); } return new CommandResult(1, result); } /** * exec shell command */ public void execShellCommand(String shellCmd) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(shellCmd) .build()); log.info("[execShellCommand] [shellCmd: {}] [result: {}]", shellCmd, result); } /** * exec shell command */ public String execShellCommand(String cmd, Integer timeout){ String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand("shell " + cmd) .build()); return result; } /** * 1. 添加自定义链 * 2. 自定义链添加到 OUTPUT 链中 */ private void addAswOutputChain() { // name=ASW_OUTPUT this.execShellCommand("shell iptables -N ASW_OUTPUT"); String outputChainResult = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildShellCommand(String.format("shell \"iptables -L OUTPUT --line-numbers | grep ASW_OUTPUT\"")) .build()); log.info("[addAswOutputChain] [ASW_OUTPUT in OUTPUT Chain] [exist: {}]", T.StrUtil.isEmpty(outputChainResult)); if (T.StrUtil.isEmpty(outputChainResult)) { // ASW_OUTPUT 添加到 OUTPUT 链中 this.execShellCommand("shell iptables -A OUTPUT -j ASW_OUTPUT"); } } /** * ASW_OUTPUT chain rules * iptables -nL ASW_OUTPUT --line-numbers */ public List listAcl() { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesLnRulesCommand("ASW_OUTPUT") .build()); List chainList = T.ListUtil.list(true); String[] lines = result.split("\\n"); for (String line : lines) { String[] split = line.split("\\s+"); String chainIndex = T.StrUtil.trim(split[0]); if (T.StrUtil.isNumeric(chainIndex)) { String protocol = T.StrUtil.trim(split[2]); Map m = T.MapUtil.builder() .put("num", Integer.valueOf(chainIndex)) .put("protocol", protocol) .build(); String destIp = T.StrUtil.trim(split[5]); if (!T.StrUtil.equals("0.0.0.0/0", destIp)) { m.put("ip", destIp); } if (split.length == 8) { String dpt = T.StrUtil.trim(split[7]); dpt = dpt.replaceAll("dpt:", ""); if (T.StrUtil.isNumeric(chainIndex)) { m.put("port", Integer.valueOf(dpt)); } } chainList.add(m); } } return chainList; } /** * add chain rule * iptables -A ASW_OUTPUT -p prot -d ip --dport port -j ACCEPT */ public void addAcl(String protocol, String ip, String port) { // add chain this.addAswOutputChain(); // add chain ruls String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesAddRuleCommand("ASW_OUTPUT", protocol, ip, port) .build()); log.info("[addAcl] [protocol: {}] [ip: {}] [port: {}] [result: {}]", protocol, ip, port, result); } /** * del chain rule * iptables -D ASW_OUTPUT -p prot -d ip --dport port -j ACCEPT */ public void deleteAcl(String protocol, String ip, String port) { // add chain ruls String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesDelRuleCommand("ASW_OUTPUT", protocol, ip, port) .build()); log.info("[deleteAcl] [protocol: {}] [ip: {}] [port: {}] [result: {}]", protocol, ip, port, result); } public CommandResult clearAppData(String packageName) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildClearAppDateCommand(packageName) .build()); log.info("[clearAppData] [packageName: {}]", packageName); return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result); } /** * flushAcl * iptables -F ASW_OUTPUT */ public CommandResult flushAcl() { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildIptablesFlushRuleCommand("ASW_OUTPUT") .build()); log.info("[flushAcl] [result: {}]", result); return new CommandResult(T.StrUtil.isNotEmpty(result) ? 1 : 0, result); } public boolean findPackageInstall(String packageName) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildCheckPackage(packageName) .build() ); return T.StrUtil.equals(result, packageName); } public List findPackageNameList() { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildFindPackageNameList() .build() ); List packageNameList = T.ListUtil.list(true); String prefix = "package:"; String[] lines = result.split("\\n"); for (String line : lines) { String packageName = T.StrUtil.trim(line.substring(prefix.length())); if (T.StrUtil.equals(DEFAULT_DROIDVNC_NG_PKG_NAME, packageName)) continue; packageNameList.add(packageName); } return packageNameList; } public CommandResult startApp(String packageName) { String result = commandExec.exec(AdbCommandBuilder.builder() .serial(this.getSerial()) .buildStartAPP(packageName) .build() ); log.info("[startApp] [result: {}]", result); return new CommandResult(T.StrUtil.isNotEmpty(result) ? 1 : 0, result); } private synchronized ExecutorService getThreadPool() { if (threadPool == null) { threadPool = new ThreadPoolExecutor( 5, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue(10000), new NamedThreadFactory("API-", true)); } return threadPool; } public CommandResult execPlaybook(String scriptPath, String tid, String packageName, String type, File logFile) { log.info("[execPlaybook] [begin!] [serial:{}]", this.getSerial()); List command; File environment = null; if (T.StrUtil.equals(type, "python")){ // check requirements.txt Path parent = Paths.get(scriptPath).getParent(); Path resolve = parent.resolve("requirements.txt"); command = new PythonCommandBuilder("python") .buildRunPythonScript(scriptPath, tid, packageName, this.getSerial()) .build(); if (T.FileUtil.exist(resolve.toString())){ // create venv environment = T.FileUtil.file(parent.toString(), "environment"); commandExec.exec(new PythonCommandBuilder("python").buildCreateVenv(environment.getAbsolutePath()).build()); commandExec.exec(new PythonCommandBuilder(T.StrUtil.concat(true, environment.getAbsolutePath(), "/bin/pip")).buildUpgradePip().build()); commandExec.exec(new PythonCommandBuilder(T.StrUtil.concat(true, environment.getAbsolutePath(), "/bin/pip")).buildInstallRequirements(resolve.toString()).build()); command = new PythonCommandBuilder(T.StrUtil.concat(true, environment.getAbsolutePath(), "/bin/python")) .buildRunPythonScript(scriptPath, tid, packageName, this.getSerial()) .build(); } }else { command = new PythonCommandBuilder("python") .buildRunAirScript(ANDROID_LAUNCHER, scriptPath, tid, packageName, this.getSerial()) .build(); } Process process = commandExec.execForProcess(command); T.FileUtil.appendString(T.StrUtil.concat(true, "$ ", command.stream().collect(Collectors.joining(" ")), "\n"), logFile, "UTF-8"); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; try { inputStreamReader = new InputStreamReader(process.getInputStream(), "UTF-8"); bufferedReader = new BufferedReader(inputStreamReader); String line; while ((line = bufferedReader.readLine()) != null) { if (T.ObjectUtil.isNotNull(interrupt) && interrupt){ log.info("[PlaybookRunnable] [execPlaybook] [stop exec playbook]"); process.destroyForcibly(); } // 处理每一行输出 T.FileUtil.appendString(T.StrUtil.concat(true, line, "\n"), logFile, "UTF-8"); } int exitCode = process.waitFor(); return new CommandResult(exitCode, T.StrUtil.EMPTY); } catch (Exception e) { process.destroyForcibly(); throw new APIException(RCode.ERROR); }finally { T.FileUtil.del(environment); T.IoUtil.close(inputStreamReader); T.IoUtil.close(bufferedReader); } } }