This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
appsketch-works-device-api/src/main/java/net/geedge/api/util/AdbUtil.java

959 lines
38 KiB
Java
Raw Normal View History

2024-08-22 09:22:52 +08:00
package net.geedge.api.util;
import cn.hutool.core.codec.Base32Codec;
import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.log.Log;
2024-09-04 14:19:02 +08:00
import net.geedge.api.entity.EnvApiYml;
2024-08-22 09:22:52 +08:00
import net.geedge.common.APIException;
import net.geedge.common.Constant;
import net.geedge.common.RCode;
import net.geedge.common.T;
import java.io.*;
2024-11-26 10:08:24 +08:00
import java.nio.file.Path;
2024-08-22 09:22:52 +08:00
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;
2024-08-22 09:22:52 +08:00
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";
2024-08-22 09:22:52 +08:00
private String serial;
private String host;
private Integer port;
private Integer vncPort;
private CommandExec commandExec;
private boolean interrupt;
2024-08-22 09:22:52 +08:00
private ExecutorService threadPool;
2024-08-22 09:22:52 +08:00
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;
}
2024-08-22 09:22:52 +08:00
public record CommandResult(Integer exitCode, String output) {
}
public AdbUtil(EnvApiYml.Adb adb, CommandExec commandExec) {
2024-08-22 09:22:52 +08:00
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;
2024-08-22 09:22:52 +08:00
}
public static AdbUtil getInstance(EnvApiYml.Adb connInfo, CommandExec commandExec) {
2024-08-22 09:22:52 +08:00
if (instance == null) {
synchronized (AdbUtil.class) {
if (instance == null) {
instance = new AdbUtil(connInfo, commandExec);
2024-08-22 09:22:52 +08:00
}
}
}
return instance;
}
/**
* connect
*/
public boolean connect() {
2024-08-22 09:22:52 +08:00
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;
2024-08-22 09:22:52 +08:00
}
} else {
// remote
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.buildConnectCommand(this.host, this.port)
.build());
log.info("[connect] [result: {}]", result);
if (!T.StrUtil.contains(result, "connected")) {
return false;
2024-08-22 09:22:52 +08:00
}
}
return true;
}
/**
* init
* su root
* install droidVNC NG
*/
public void init(boolean install) {
2024-08-22 09:22:52 +08:00
// adb root
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.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");
2024-09-12 15:51:50 +08:00
// 添加自定义链
this.addAswOutputChain();
2024-08-22 09:22:52 +08:00
}
/**
* status
*
* @return
*/
public Map<Object, Object> status() {
Map<Object, Object> m = T.MapUtil.builder()
.put("platform", "android")
.build();
AdbDevice device = this.getAdbDevice();
m.put("status", device.isAvailable() ? "online" : "offline");
Map<String, String> 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<String, String> 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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.buildDevicesCommand()
.build()
);
List<AdbDevice> 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<String, String> getProp() {
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildGetpropCommand()
.build()
);
Map<String, String> 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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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<Map> listDir(String path) {
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.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<CompletableFuture<Map>> futureList = T.ListUtil.list(false);
List<Map> 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;
2024-08-22 09:22:52 +08:00
futureList.add(
CompletableFuture.supplyAsync(() -> {
String statResult = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildShellCommand(statCommand.replaceAll("\\\\", "/"))
.build()
);
// reverse result
List<String> list = Arrays.asList(statResult.split("\\s+"));
Collections.reverse(list);
String fullName = list.get(7).replaceAll("'|`", "");
2024-08-22 09:22:52 +08:00
Map<String, Object> 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)))
2024-08-22 09:22:52 +08:00
.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"))
2024-08-22 09:22:52 +08:00
.build());
return relMap;
}, getThreadPool())
);
Map<Object, Object> 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<Map> listApp(String arg) {
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildPmListPackagesCommand(arg)
.build()
);
List<Map> listApp = T.ListUtil.list(true);
List<CompletableFuture<Map>> 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;
2024-08-22 09:22:52 +08:00
String dumpsysResult = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.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());
2024-08-22 09:22:52 +08:00
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;
// }
2024-08-22 09:22:52 +08:00
Map<String, Object> relMap = T.MapUtil.newHashMap();
relMap.put("pkg", packageName);
relMap.put("value", T.MapUtil.builder()
.put("name", appName)
// .put("icon", base64IconDate)
2024-08-22 09:22:52 +08:00
.build());
return relMap;
} catch (Exception e) {
log.error(e, "[listApp] [parse apk] [pkg: {}]", packageName);
}
return null;
}, getThreadPool())
);
}
Map<Object, Object> 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()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildInstallCommand(localFilePath, isDebugApk, isReInstall)
.build());
2024-08-22 09:22:52 +08:00
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()
2024-08-22 09:22:52 +08:00
.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);
}
2024-08-22 09:22:52 +08:00
/**
* iptables -F
* iptables -X
*/
@Deprecated
2024-08-22 09:22:52 +08:00
private void cleanIptables() {
// Delete all rules in chain or all chains
commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildShellCommand("shell iptables -F")
.build()
);
// Delete user-defined chain
commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildShellCommand("shell iptables -X")
.build()
);
}
/**
* list tcpdump
*/
public List<Map> 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<Map> 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<Object, Object> 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;
}
2024-08-22 09:22:52 +08:00
/**
* start Tcpdump
* tcpdump pcap
*/
public CommandResult startTcpdump(String packageName) {
2024-08-22 09:22:52 +08:00
String taskId = T.IdUtil.fastSimpleUUID();
if (T.StrUtil.isNotEmpty(packageName)) {
log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName);
String dumpsysResult = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath))
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.serial(this.getSerial())
.buildShellCommand(String.format("shell tcpdump not port %s -w %s &", this.vncPort, pcapFilePath))
2024-08-22 09:22:52 +08:00
.build());
}
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-08-22 09:22:52 +08:00
.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()
2024-08-22 09:22:52 +08:00
.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);
2024-08-22 09:22:52 +08:00
}
/**
* 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);
}
2024-08-22 09:22:52 +08:00
/**
* exec shell command
*/
public String execShellCommand(String cmd, Integer timeout){
2024-11-04 11:24:41 +08:00
String result = commandExec.exec(AdbCommandBuilder.builder()
.serial(this.getSerial())
.buildShellCommand("shell " + cmd)
.build());
2024-11-04 11:24:41 +08:00
return result;
}
2024-09-12 15:51:50 +08:00
/**
* 1. 添加自定义链
* 2. 自定义链添加到 OUTPUT 链中
*/
private void addAswOutputChain() {
// name=ASW_OUTPUT
this.execShellCommand("shell iptables -N ASW_OUTPUT");
2024-09-12 15:51:50 +08:00
String outputChainResult = commandExec.exec(AdbCommandBuilder.builder()
2024-09-12 15:51:50 +08:00
.serial(this.getSerial())
.buildShellCommand(String.format("shell \"iptables -L OUTPUT --line-numbers | grep ASW_OUTPUT\""))
.build());
2024-11-04 11:24:41 +08:00
log.info("[addAswOutputChain] [ASW_OUTPUT in OUTPUT Chain] [exist: {}]", T.StrUtil.isEmpty(outputChainResult));
2024-09-12 15:51:50 +08:00
if (T.StrUtil.isEmpty(outputChainResult)) {
// ASW_OUTPUT 添加到 OUTPUT 链中
this.execShellCommand("shell iptables -A OUTPUT -j ASW_OUTPUT");
2024-09-12 15:51:50 +08:00
}
}
/**
* ASW_OUTPUT chain rules
* iptables -nL ASW_OUTPUT --line-numbers
*/
public List<Map> listAcl() {
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-09-12 15:51:50 +08:00
.serial(this.getSerial())
.buildIptablesLnRulesCommand("ASW_OUTPUT")
.build());
List<Map> 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<Object, Object> 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()
2024-09-12 15:51:50 +08:00
.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()
2024-09-12 15:51:50 +08:00
.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);
}
2024-09-12 15:51:50 +08:00
/**
* flushAcl
* iptables -F ASW_OUTPUT
*/
public CommandResult flushAcl() {
String result = commandExec.exec(AdbCommandBuilder.builder()
2024-09-12 15:51:50 +08:00
.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<String> 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);
2024-09-12 15:51:50 +08:00
}
2024-08-22 09:22:52 +08:00
private synchronized ExecutorService getThreadPool() {
if (threadPool == null) {
threadPool = new ThreadPoolExecutor(
5,
10,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10000),
new NamedThreadFactory("API-", true));
}
return threadPool;
}
2024-11-26 10:08:24 +08:00
public CommandResult execPlaybook(String scriptPath, String tid, String packageName, String type, File logFile) {
log.info("[execPlaybook] [begin!] [serial:{}]", this.getSerial());
2024-11-26 10:08:24 +08:00
List<String> 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);
2024-11-04 11:24:41 +08:00
T.FileUtil.appendString(T.StrUtil.concat(true, "$ ", command.stream().collect(Collectors.joining(" ")), "\n"), logFile, "UTF-8");
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
2024-11-04 11:24:41 +08:00
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();
}
2024-11-04 11:24:41 +08:00
// 处理每一行输出
T.FileUtil.appendString(T.StrUtil.concat(true, line, "\n"), logFile, "UTF-8");
}
int exitCode = process.waitFor();
2024-11-04 11:24:41 +08:00
return new CommandResult(exitCode, T.StrUtil.EMPTY);
} catch (Exception e) {
process.destroyForcibly();
throw new APIException(RCode.ERROR);
}finally {
2024-11-26 10:08:24 +08:00
T.FileUtil.del(environment);
2024-11-04 11:24:41 +08:00
T.IoUtil.close(inputStreamReader);
T.IoUtil.close(bufferedReader);
}
}
2024-08-22 09:22:52 +08:00
}