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
2024-11-26 10:08:24 +08:00

959 lines
38 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<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()
.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<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()
.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()
.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<Map> 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<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;
futureList.add(
CompletableFuture.supplyAsync(() -> {
String statResult = commandExec.exec(AdbCommandBuilder.builder()
.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("'|`", "");
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)))
.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<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()
.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;
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<String, Object> 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<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()
.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<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;
}
/**
* 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<Map> listAcl() {
String result = commandExec.exec(AdbCommandBuilder.builder()
.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()
.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<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);
}
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;
}
public CommandResult execPlaybook(String scriptPath, String tid, String packageName, String type, File logFile) {
log.info("[execPlaybook] [begin!] [serial:{}]", this.getSerial());
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);
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);
}
}
}