init: ASW-37 初始化 device-api
This commit is contained in:
84
src/main/java/net/geedge/api/config/VncProxyHandler.java
Normal file
84
src/main/java/net/geedge/api/config/VncProxyHandler.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package net.geedge.api.config;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import net.geedge.api.entity.DeviceApiYml;
|
||||
import net.geedge.common.T;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
public class VncProxyHandler extends TextWebSocketHandler {
|
||||
|
||||
private static final Log log = Log.get();
|
||||
|
||||
private DeviceApiYml.Vnc vnc;
|
||||
|
||||
public VncProxyHandler(DeviceApiYml.Vnc vnc) {
|
||||
this.vnc = vnc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
log.info("[afterConnectionEstablished] [WebSocket connection established] [websocket uri: {}]", session.getUri());
|
||||
super.afterConnectionEstablished(session);
|
||||
|
||||
// connect to VNC Server
|
||||
Socket vncSocket = new Socket(vnc.getHost(), vnc.getPort());
|
||||
session.getAttributes().put("vncSocket", vncSocket);
|
||||
log.info("[afterConnectionEstablished] [vnc server: {}] [isConnected: {}]", T.JSONUtil.toJsonStr(vnc), vncSocket.isConnected());
|
||||
|
||||
// vnc server -> web
|
||||
T.ThreadUtil.execute(() -> {
|
||||
this.forwardFromVncToWeb(session, vncSocket);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* vnc server -> web
|
||||
*
|
||||
* @param session
|
||||
* @param vncSocket
|
||||
*/
|
||||
private void forwardFromVncToWeb(WebSocketSession session, Socket vncSocket) {
|
||||
try (InputStream inputStream = vncSocket.getInputStream()) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
session.sendMessage(new BinaryMessage(buffer, 0, bytesRead, true));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[forwardFromVncToWeb] [error]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
|
||||
try {
|
||||
// web -> vnc server
|
||||
Socket vncSocket = (Socket) session.getAttributes().get("vncSocket");
|
||||
if (vncSocket != null && !vncSocket.isClosed()) {
|
||||
OutputStream outputStream = vncSocket.getOutputStream();
|
||||
outputStream.write(message.getPayload().array());
|
||||
outputStream.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[handleBinaryMessage] [error]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
|
||||
log.info("[afterConnectionClosed] [WebSocket connection closed] [websocket uri: {}]", session.getUri());
|
||||
Socket vncSocket = (Socket) session.getAttributes().get("vncSocket");
|
||||
if (vncSocket != null && !vncSocket.isClosed()) {
|
||||
vncSocket.close();
|
||||
}
|
||||
super.afterConnectionClosed(session, status);
|
||||
}
|
||||
}
|
||||
21
src/main/java/net/geedge/api/config/WebSocketConfig.java
Normal file
21
src/main/java/net/geedge/api/config/WebSocketConfig.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.geedge.api.config;
|
||||
|
||||
import net.geedge.api.entity.DeviceApiYml;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Autowired
|
||||
private DeviceApiYml deviceApiYml;
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new VncProxyHandler(deviceApiYml.getVnc()), "/api/v1/device/websocket").setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
166
src/main/java/net/geedge/api/controller/APIController.java
Normal file
166
src/main/java/net/geedge/api/controller/APIController.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package net.geedge.api.controller;
|
||||
|
||||
import cn.hutool.core.codec.Base32Codec;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.geedge.api.entity.DeviceApiYml;
|
||||
import net.geedge.api.util.AdbUtil;
|
||||
import net.geedge.common.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/device")
|
||||
public class APIController {
|
||||
|
||||
private final AdbUtil adbUtil;
|
||||
|
||||
@Autowired
|
||||
public APIController(DeviceApiYml deviceApiYml) {
|
||||
this.adbUtil = AdbUtil.getInstance(deviceApiYml.getAdb());
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
public R status() {
|
||||
return R.ok(adbUtil.status());
|
||||
}
|
||||
|
||||
@PostMapping("/file")
|
||||
public R push(@RequestParam(value = "file") MultipartFile file, @RequestParam String path) throws IOException {
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
AdbUtil.CommandResult result = adbUtil.push(tempFile.getAbsolutePath(), path);
|
||||
if (0 != result.exitCode()) {
|
||||
return R.error(result.output());
|
||||
}
|
||||
} finally {
|
||||
T.FileUtil.del(tempFile);
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/file/{fileId}")
|
||||
public void pull(@PathVariable String fileId, HttpServletResponse response) throws IOException {
|
||||
byte[] decode = Base32Codec.Base32Decoder.DECODER.decode(fileId);
|
||||
String filePath = T.StrUtil.str(decode, T.CharsetUtil.CHARSET_UTF_8);
|
||||
String fileName = T.FileUtil.getName(filePath);
|
||||
|
||||
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, fileName);
|
||||
try {
|
||||
AdbUtil.CommandResult result = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException(result.output());
|
||||
}
|
||||
if (T.FileUtil.isDirectory(tempFile)) {
|
||||
File zip = T.ZipUtil.zip(tempFile);
|
||||
try {
|
||||
T.ResponseUtil.downloadFile(response, zip.getName(), T.FileUtil.readBytes(zip));
|
||||
} finally {
|
||||
T.FileUtil.del(zip);
|
||||
}
|
||||
} else {
|
||||
T.ResponseUtil.downloadFile(response, fileName, T.FileUtil.readBytes(tempFile));
|
||||
}
|
||||
} finally {
|
||||
T.FileUtil.del(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/file")
|
||||
public R listDir(@RequestParam(defaultValue = "/") String path) {
|
||||
List<Map> listDir = adbUtil.listDir(path);
|
||||
Map<Object, Object> data = T.MapUtil.builder()
|
||||
.put("path", path)
|
||||
.put("records", listDir)
|
||||
.build();
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
@GetMapping("/app")
|
||||
public R listApp(@RequestParam(required = false) String arg) {
|
||||
return R.ok().putData("records", adbUtil.listApp(arg));
|
||||
}
|
||||
|
||||
@PostMapping("/app")
|
||||
public R install(@RequestParam(value = "file", required = false) MultipartFile file,
|
||||
@RequestParam(required = false) String path) throws IOException {
|
||||
if (file != null) {
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
AdbUtil.CommandResult result = adbUtil.install(tempFile.getAbsolutePath(), true, true);
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException(result.output());
|
||||
}
|
||||
return R.ok();
|
||||
} finally {
|
||||
T.FileUtil.del(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (T.StrUtil.isNotEmpty(path)) {
|
||||
AdbUtil.CommandResult result = adbUtil.install(path, true, true);
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException(result.output());
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
return R.error(RCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@DeleteMapping("/app")
|
||||
public R uninstall(@RequestParam String packageName) {
|
||||
AdbUtil.CommandResult result = adbUtil.uninstall(packageName);
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException(result.output());
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/pcap")
|
||||
public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) {
|
||||
AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName);
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException("exec tcpdump error");
|
||||
}
|
||||
return R.ok().putData("id", result.output());
|
||||
}
|
||||
|
||||
@DeleteMapping("/pcap")
|
||||
public void stopTcpdump(@RequestParam String id,
|
||||
@RequestParam(required = false, defaultValue = "false") Boolean returnFile,
|
||||
HttpServletResponse response) throws IOException {
|
||||
AdbUtil.CommandResult result = adbUtil.stopTcpdump(id);
|
||||
if (0 != result.exitCode()) {
|
||||
throw new APIException(result.output());
|
||||
}
|
||||
|
||||
if (returnFile) {
|
||||
// response pcap file
|
||||
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap");
|
||||
try {
|
||||
String filePath = "/data/local/tmp/" + id + ".pcap";
|
||||
AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
||||
if (0 != pulled.exitCode()) {
|
||||
throw new APIException(pulled.output());
|
||||
}
|
||||
T.ResponseUtil.downloadFile(response, tempFile.getName(), T.FileUtil.readBytes(tempFile));
|
||||
} finally {
|
||||
T.FileUtil.del(tempFile);
|
||||
}
|
||||
} else {
|
||||
// response taskid
|
||||
response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/main/java/net/geedge/api/entity/DeviceApiYml.java
Normal file
32
src/main/java/net/geedge/api/entity/DeviceApiYml.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.geedge.api.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceApiYml {
|
||||
|
||||
private Device device;
|
||||
private Adb adb;
|
||||
private Vnc vnc;
|
||||
|
||||
@Data
|
||||
public class Device {
|
||||
String type;
|
||||
String platform;
|
||||
String root;
|
||||
}
|
||||
|
||||
@Data
|
||||
public class Adb {
|
||||
String serial;
|
||||
String host;
|
||||
Integer port;
|
||||
}
|
||||
|
||||
@Data
|
||||
public class Vnc {
|
||||
String host;
|
||||
Integer port;
|
||||
}
|
||||
|
||||
}
|
||||
144
src/main/java/net/geedge/api/util/AdbCommandBuilder.java
Normal file
144
src/main/java/net/geedge/api/util/AdbCommandBuilder.java
Normal file
@@ -0,0 +1,144 @@
|
||||
package net.geedge.api.util;
|
||||
|
||||
import net.geedge.common.T;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class AdbCommandBuilder {
|
||||
|
||||
private final String adbPath;
|
||||
private final List<String> command;
|
||||
|
||||
private AdbCommandBuilder(String adbPath) {
|
||||
this.adbPath = adbPath;
|
||||
this.command = new LinkedList<>();
|
||||
this.command.add(adbPath);
|
||||
}
|
||||
|
||||
public static AdbCommandBuilder builder() {
|
||||
return new AdbCommandBuilder("adb");
|
||||
}
|
||||
|
||||
public static AdbCommandBuilder builder(String adbPath) {
|
||||
return new AdbCommandBuilder(adbPath);
|
||||
}
|
||||
|
||||
public AdbCommandBuilder serial(String serial) {
|
||||
this.command.add("-s");
|
||||
this.command.add(serial);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildConnectCommand(String host, Integer port) {
|
||||
this.command.add("connect");
|
||||
this.command.add(String.format("%s:%s", host, port));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildDevicesCommand() {
|
||||
this.command.add("devices");
|
||||
this.command.add("-l");
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildRootCommand() {
|
||||
this.command.add("root");
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildGetpropCommand() {
|
||||
this.command.add("shell");
|
||||
this.command.add("getprop");
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildWmSizeCommand() {
|
||||
this.command.add("shell");
|
||||
this.command.add("wm");
|
||||
this.command.add("size");
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildCheckRootCommand() {
|
||||
this.command.add("shell");
|
||||
this.command.add("ls");
|
||||
this.command.add("/data");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定 String cmd 执行,解决命令过长阅读性较差问题
|
||||
*/
|
||||
public AdbCommandBuilder buildShellCommand(String shellCmd) {
|
||||
String[] strings = T.CommandLineUtil.translateCommandline(shellCmd);
|
||||
for (String string : strings) {
|
||||
this.command.add(string);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildPushCommand(String local, String remote) {
|
||||
this.command.add("push");
|
||||
this.command.add(local);
|
||||
this.command.add(remote);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildPullCommand(String remote, String local) {
|
||||
this.command.add("pull");
|
||||
this.command.add(remote);
|
||||
this.command.add(local);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildLsDirCommand(String path) {
|
||||
this.command.add("shell");
|
||||
this.command.add("ls");
|
||||
this.command.add("-l");
|
||||
this.command.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildPmListPackagesCommand(String arg) {
|
||||
this.command.add("shell");
|
||||
this.command.add("pm");
|
||||
this.command.add("list");
|
||||
this.command.add("packages");
|
||||
if (T.StrUtil.isNotEmpty(arg)) {
|
||||
this.command.add(arg);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildMd5sumCommand(String path) {
|
||||
this.command.add("shell");
|
||||
this.command.add("md5sum");
|
||||
this.command.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildInstallCommand(String localFilePath, boolean isDebugApk, boolean isReInstall) {
|
||||
this.command.add("install");
|
||||
if (isDebugApk) {
|
||||
this.command.add("-d");
|
||||
}
|
||||
if (isReInstall) {
|
||||
this.command.add("-r");
|
||||
}
|
||||
this.command.add(localFilePath);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdbCommandBuilder buildUnInstallCommand(String packageName) {
|
||||
this.command.add("uninstall");
|
||||
this.command.add(packageName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public List<String> build() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/java/net/geedge/api/util/AdbDevice.java
Normal file
41
src/main/java/net/geedge/api/util/AdbDevice.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package net.geedge.api.util;
|
||||
|
||||
import lombok.Data;
|
||||
import net.geedge.common.T;
|
||||
|
||||
@Data
|
||||
public class AdbDevice implements Comparable<AdbDevice> {
|
||||
|
||||
private String serial;
|
||||
private boolean available;
|
||||
|
||||
|
||||
public AdbDevice(String line) {
|
||||
String[] array = line.split(" ");
|
||||
serial = array[0];
|
||||
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
if (!T.StrUtil.isEmpty(array[i])) {
|
||||
available = "device".equals(array[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object instanceof AdbDevice)
|
||||
return serial.equals(((AdbDevice) object).serial) && available == ((AdbDevice) object).available;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AdbDevice device) {
|
||||
return serial.compareTo(device.serial);
|
||||
}
|
||||
|
||||
}
|
||||
568
src/main/java/net/geedge/api/util/AdbUtil.java
Normal file
568
src/main/java/net/geedge/api/util/AdbUtil.java
Normal file
@@ -0,0 +1,568 @@
|
||||
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.DeviceApiYml;
|
||||
import net.geedge.common.APIException;
|
||||
import net.geedge.common.Constant;
|
||||
import net.geedge.common.RCode;
|
||||
import net.geedge.common.T;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AdbUtil {
|
||||
|
||||
private static final Log log = Log.get();
|
||||
|
||||
private static AdbUtil instance;
|
||||
|
||||
private String serial;
|
||||
private String host;
|
||||
private Integer port;
|
||||
|
||||
private ExecutorService threadPool;
|
||||
|
||||
public String getSerial() {
|
||||
return T.StrUtil.isNotEmpty(this.serial) ? serial : String.format("%s:%s", this.host, this.port);
|
||||
}
|
||||
|
||||
public record CommandResult(Integer exitCode, String output) {
|
||||
}
|
||||
|
||||
private AdbUtil(DeviceApiYml.Adb adb) {
|
||||
this.serial = T.StrUtil.emptyToDefault(adb.getSerial(), "");
|
||||
this.host = adb.getHost();
|
||||
this.port = adb.getPort();
|
||||
this.connect();
|
||||
}
|
||||
|
||||
public static AdbUtil getInstance(DeviceApiYml.Adb connInfo) {
|
||||
if (instance == null) {
|
||||
synchronized (AdbUtil.class) {
|
||||
if (instance == null) {
|
||||
instance = new AdbUtil(connInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* connect
|
||||
*/
|
||||
private void 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()) {
|
||||
log.error("[device is not available, program exit]");
|
||||
Runtime.getRuntime().halt(1);
|
||||
}
|
||||
} 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")) {
|
||||
log.error("[connect error, program exit]");
|
||||
Runtime.getRuntime().halt(1);
|
||||
}
|
||||
}
|
||||
// adb root
|
||||
CommandExec.exec(AdbCommandBuilder.builder()
|
||||
.serial(this.getSerial())
|
||||
.buildRootCommand()
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 %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(3).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("permissions", Long.parseLong(list.get(2)))
|
||||
.put("cts", Long.parseLong(list.get(1)))
|
||||
.put("uts", Long.parseLong(list.get(0)))
|
||||
.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()));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* iptables -F
|
||||
* iptables -X
|
||||
*/
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* start Tcpdump
|
||||
* iptables option
|
||||
* tcpdump pcap
|
||||
*/
|
||||
public CommandResult startTcpdump(String packageName) {
|
||||
// clean iptables conf
|
||||
this.cleanIptables();
|
||||
|
||||
String taskId = T.IdUtil.fastSimpleUUID();
|
||||
String pcapFilePath = "/data/local/tmp/" + taskId + ".pcap";
|
||||
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);
|
||||
|
||||
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]");
|
||||
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||||
.serial(this.getSerial())
|
||||
.buildShellCommand(String.format("shell tcpdump -w %s &", 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 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());
|
||||
log.info("[stopTcpdump] [id: {}] [result: {}]", id, result);
|
||||
return new CommandResult(T.StrUtil.isEmpty(result) ? 0 : 1, 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;
|
||||
}
|
||||
|
||||
class CommandExec {
|
||||
public static String exec(List<String> command) {
|
||||
String str = T.RuntimeUtil.execForStr(T.CharsetUtil.CHARSET_UTF_8, command.stream().toArray(String[]::new));
|
||||
return str.stripTrailing();
|
||||
}
|
||||
|
||||
public static Process execForProcess(List<String> command) {
|
||||
Process process = T.RuntimeUtil.exec(command.stream().toArray(String[]::new));
|
||||
return process;
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/main/java/net/geedge/api/util/ApkInfo.java
Normal file
169
src/main/java/net/geedge/api/util/ApkInfo.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package net.geedge.api.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ApkInfo {
|
||||
|
||||
public static final String APPLICATION_ICON_120 = "application-icon-120";
|
||||
public static final String APPLICATION_ICON_160 = "application-icon-160";
|
||||
public static final String APPLICATION_ICON_240 = "application-icon-240";
|
||||
public static final String APPLICATION_ICON_320 = "application-icon-320";
|
||||
|
||||
// 所需设备属性
|
||||
private List<String> features;
|
||||
// 图标
|
||||
private String icon;
|
||||
// 各分辨率下图标路径
|
||||
private Map<String, String> icons;
|
||||
// 应用程序名
|
||||
private String label;
|
||||
// 入口Activity
|
||||
private String launchableActivity;
|
||||
// 支持的Android平台最低版本号
|
||||
private String minSdkVersion;
|
||||
// 主包名
|
||||
private String packageName;
|
||||
// 支持的SDK版本
|
||||
private String sdkVersion;
|
||||
// Apk文件大小(字节)
|
||||
private long size;
|
||||
// 目标SDK版本
|
||||
private String targetSdkVersion;
|
||||
// 所需权限
|
||||
private List<String> usesPermissions;
|
||||
// 内部版本号
|
||||
private String versionCode;
|
||||
// 外部版本号
|
||||
private String versionName;
|
||||
|
||||
public ApkInfo() {
|
||||
this.features = new ArrayList<>();
|
||||
this.icons = new HashMap<>();
|
||||
this.usesPermissions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<String> getFeatures() {
|
||||
return features;
|
||||
}
|
||||
|
||||
public void setFeatures(List<String> features) {
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
public void addToFeatures(String feature) {
|
||||
this.features.add(feature);
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public Map<String, String> getIcons() {
|
||||
return icons;
|
||||
}
|
||||
|
||||
public void setIcons(Map<String, String> icons) {
|
||||
this.icons = icons;
|
||||
}
|
||||
|
||||
public void addToIcons(String key, String value) {
|
||||
this.icons.put(key, value);
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLaunchableActivity() {
|
||||
return launchableActivity;
|
||||
}
|
||||
|
||||
public void setLaunchableActivity(String launchableActivity) {
|
||||
this.launchableActivity = launchableActivity;
|
||||
}
|
||||
|
||||
public String getMinSdkVersion() {
|
||||
return minSdkVersion;
|
||||
}
|
||||
|
||||
public void setMinSdkVersion(String minSdkVersion) {
|
||||
this.minSdkVersion = minSdkVersion;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public String getSdkVersion() {
|
||||
return sdkVersion;
|
||||
}
|
||||
|
||||
public void setSdkVersion(String sdkVersion) {
|
||||
this.sdkVersion = sdkVersion;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getTargetSdkVersion() {
|
||||
return targetSdkVersion;
|
||||
}
|
||||
|
||||
public void setTargetSdkVersion(String targetSdkVersion) {
|
||||
this.targetSdkVersion = targetSdkVersion;
|
||||
}
|
||||
|
||||
public List<String> getUsesPermissions() {
|
||||
return usesPermissions;
|
||||
}
|
||||
|
||||
public void setUsesPermissions(List<String> usesPermissions) {
|
||||
this.usesPermissions = usesPermissions;
|
||||
}
|
||||
|
||||
public void addToUsesPermissions(String usesPermission) {
|
||||
this.usesPermissions.add(usesPermission);
|
||||
}
|
||||
|
||||
public String getVersionCode() {
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
public void setVersionCode(String versionCode) {
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public String getVersionName() {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
this.versionName = versionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApkInfo [features=" + features + ", icon=" + icon + ", icons=" + icons + ", label=" + label + ", launchableActivity=" + launchableActivity + ", minSdkVersion=" + minSdkVersion + ", packageName=" + packageName + ", sdkVersion=" + sdkVersion + ", size=" + size + ", targetSdkVersion=" + targetSdkVersion + ", usesPermissions=" + usesPermissions + ", versionCode=" + versionCode + ", versionName=" + versionName + "]";
|
||||
}
|
||||
|
||||
}
|
||||
145
src/main/java/net/geedge/api/util/ApkUtil.java
Normal file
145
src/main/java/net/geedge/api/util/ApkUtil.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package net.geedge.api.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import net.geedge.common.Constant;
|
||||
import net.geedge.common.T;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Base64;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ApkUtil {
|
||||
|
||||
private static final Log log = Log.get();
|
||||
|
||||
public static final String APPLICATION = "application:";
|
||||
public static final String APPLICATION_ICON = "application-icon";
|
||||
public static final String APPLICATION_LABEL = "application-label";
|
||||
public static final String APPLICATION_LABEL_N = "application: label";
|
||||
public static final String DENSITIES = "densities";
|
||||
public static final String LAUNCHABLE_ACTIVITY = "launchable";
|
||||
public static final String PACKAGE = "package";
|
||||
public static final String SDK_VERSION = "sdkVersion";
|
||||
public static final String SUPPORTS_ANY_DENSITY = "support-any-density";
|
||||
public static final String SUPPORTS_SCREENS = "support-screens";
|
||||
public static final String TARGET_SDK_VERSION = "targetSdkVersion";
|
||||
public static final String VERSION_CODE = "versionCode";
|
||||
public static final String VERSION_NAME = "versionName";
|
||||
public static final String USES_FEATURE = "uses-feature";
|
||||
public static final String USES_IMPLIED_FEATURE = "uses-implied-feature";
|
||||
public static final String USES_PERMISSION = "uses-permission";
|
||||
|
||||
private static final String SPLIT_REGEX = "(: )|(=')|(' )|'";
|
||||
|
||||
private ProcessBuilder builder;
|
||||
// aapt 所在目录
|
||||
private String aaptToolPath = "aapt";
|
||||
|
||||
public ApkUtil() {
|
||||
builder = new ProcessBuilder();
|
||||
builder.redirectErrorStream(true);
|
||||
}
|
||||
|
||||
public String getAaptToolPath() {
|
||||
return aaptToolPath;
|
||||
}
|
||||
|
||||
public void setAaptToolPath(String aaptToolPath) {
|
||||
this.aaptToolPath = aaptToolPath;
|
||||
}
|
||||
|
||||
public ApkInfo parseApk(String apkPath) {
|
||||
String aaptTool = aaptToolPath;
|
||||
Process process = null;
|
||||
InputStream inputStream = null;
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
process = builder.command(aaptTool, "d", "badging", apkPath).start();
|
||||
inputStream = process.getInputStream();
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
apkInfo.setSize(new File(apkPath).length());
|
||||
String temp = null;
|
||||
while ((temp = bufferedReader.readLine()) != null) {
|
||||
setApkInfoProperty(apkInfo, temp);
|
||||
}
|
||||
return apkInfo;
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[parseApk] [error]");
|
||||
return null;
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
T.IoUtil.close(inputStream);
|
||||
T.IoUtil.close(bufferedReader);
|
||||
}
|
||||
}
|
||||
|
||||
private void setApkInfoProperty(ApkInfo apkInfo, String source) {
|
||||
if (source.startsWith(APPLICATION)) {
|
||||
String[] rs = source.split("( icon=')|'");
|
||||
apkInfo.setIcon(rs[rs.length - 1]);
|
||||
} else if (source.startsWith(APPLICATION_ICON)) {
|
||||
apkInfo.addToIcons(getKeyBeforeColon(source), getPropertyInQuote(source));
|
||||
} else if (source.startsWith(APPLICATION_LABEL)) {
|
||||
apkInfo.setLabel(getPropertyInQuote(source));
|
||||
} else if (source.startsWith(LAUNCHABLE_ACTIVITY)) {
|
||||
apkInfo.setLaunchableActivity(getPropertyInQuote(source));
|
||||
} else if (source.startsWith(PACKAGE)) {
|
||||
String[] packageInfo = source.split(SPLIT_REGEX);
|
||||
apkInfo.setPackageName(packageInfo[2]);
|
||||
apkInfo.setVersionCode(packageInfo[4]);
|
||||
apkInfo.setVersionName(packageInfo[6]);
|
||||
} else if (source.startsWith(SDK_VERSION)) {
|
||||
apkInfo.setSdkVersion(getPropertyInQuote(source));
|
||||
} else if (source.startsWith(TARGET_SDK_VERSION)) {
|
||||
apkInfo.setTargetSdkVersion(getPropertyInQuote(source));
|
||||
} else if (source.startsWith(USES_PERMISSION)) {
|
||||
apkInfo.addToUsesPermissions(getPropertyInQuote(source));
|
||||
} else if (source.startsWith(USES_FEATURE)) {
|
||||
apkInfo.addToFeatures(getPropertyInQuote(source));
|
||||
}
|
||||
}
|
||||
|
||||
private String getKeyBeforeColon(String source) {
|
||||
return source.substring(0, source.indexOf(':'));
|
||||
}
|
||||
|
||||
private String getPropertyInQuote(String source) {
|
||||
int index = source.indexOf("'") + 1;
|
||||
return source.substring(index, source.indexOf('\'', index));
|
||||
}
|
||||
|
||||
public String extractFileFromApk(String apkPath, String fileName) {
|
||||
ZipFile zipFile = null;
|
||||
File tempIconFile = T.FileUtil.file(Constant.TEMP_PATH, T.IdUtil.fastSimpleUUID());
|
||||
try {
|
||||
zipFile = new ZipFile(apkPath);
|
||||
ZipEntry entry = zipFile.getEntry(fileName);
|
||||
InputStream inputStream = zipFile.getInputStream(entry);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tempIconFile), 1024);
|
||||
byte[] b = new byte[1024];
|
||||
BufferedInputStream bis = new BufferedInputStream(inputStream, 1024);
|
||||
while (bis.read(b) != -1) {
|
||||
bos.write(b);
|
||||
}
|
||||
IoUtil.flush(bos);
|
||||
T.IoUtil.close(bos);
|
||||
T.IoUtil.close(bis);
|
||||
T.IoUtil.close(inputStream);
|
||||
T.IoUtil.close(zipFile);
|
||||
|
||||
String base64Str = Base64.getEncoder().encodeToString(T.FileUtil.readBytes(tempIconFile));
|
||||
return base64Str;
|
||||
} catch (IOException e) {
|
||||
log.error(e, "[extractFileFromApk] [error]");
|
||||
} finally {
|
||||
T.FileUtil.del(tempIconFile);
|
||||
T.IoUtil.close(zipFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user