harmony设备接入上位机、投屏,自动化、以及任务、计划等

hz_1122
yineng.huang 2025-02-13 18:16:37 +08:00
parent 8beaa4b87a
commit 565c5f1ee9
160 changed files with 16057 additions and 1098 deletions

View File

@ -154,8 +154,8 @@ public class DebugServiceImpl implements DebugService {
Object type = request.getData().get("type");
boolean mobileFlag = false;
if (type != null) {
// 0-接口 1- b/s 2 c/s 3 安卓 4 ios
mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + "");
// 0-接口 1- b/s 2 c/s 3 安卓 4 ios 5 harmony
mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + "") || "5".equalsIgnoreCase(type+"");
}
if (mobileFlag && DebugerConst.CommandConst.CMD_DEVICE_INIT.equalsIgnoreCase(request.getCmd())) { //设置应用下载的url
Map<String, Object> data = request.getData();

View File

@ -135,6 +135,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
driver = createAndroidDriver(appiumServerUrl, appiumHost + ":8808", deviceInfo.getDeviceId(), null);
} else if ("1".equalsIgnoreCase(deviceInfo.getPlatform()) || "ios".equalsIgnoreCase(deviceInfo.getPlatform())) {
driver = createIosDriver(appiumServerUrl, deviceInfo, null);
}else if ("2".equalsIgnoreCase(deviceInfo.getPlatform()) || "harmony".equalsIgnoreCase(deviceInfo.getPlatform())) {
driver = createHarmonyDriver();
}
if (driver != null) {
log.info("Driver for device[{}] created successfully.", deviceInfo.getDeviceId());
@ -145,6 +147,18 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
return driver;
}
private DeviceDriver createHarmonyDriver() {
MobileDeviceDriver driver = null;
log.warn("设备【{}】采用自研方式自动化", deviceInfo.getDeviceId());
try {
driver = new MobileDeviceDriver(this.remoteAddress,deviceInfo.getDeviceId(),"harmony");
} catch (Exception e) {
log.error("设备【{}】创建MobileDriver失败地址{}", deviceInfo.getDeviceId(),this.remoteAddress, e);
throw new ExecuteException("设备驱动创建失败,请稍后再试");
}
return driver;
}
private DeviceDriver createIosDriver(URL appiumServerUrl, DeviceInfo deviceInfo, Map<String, String> others) {
MobileAutomationSelectorConfig selectorConfig = SpringUtils.getBean(MobileAutomationSelectorConfig.class);
log.info("拿到的自动化类型:{}", selectorConfig.getType());
@ -501,21 +515,11 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
watchAppActive = new StopWatch();
watchAppActive.start();
}
if (this.deviceDriver instanceof MobileDriver) {
log.debug("设备【{}】采取自研自动化....................",this.deviceInfo.getDeviceId());
String uuid = this.deviceInfo.getDeviceId();
IosUtil.activeApp(uuid, appPackage, remoteAddress);
} else {
log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId());
AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) this.deviceDriver;
try {
String uuid = (String) driver.getCapabilities().getCapability(UDID);
if (driver instanceof AndroidDriver) {
AndroidUtil.activeApp(uuid, appPackage, remoteAddress);
} else if (driver instanceof IOSDriver) {
IosUtil.activeApp(uuid, appPackage, remoteAddress);
}
} finally {
try {
log.debug("设备【{}】开始启动App....................", this.deviceInfo.getDeviceId());
PhoneUtil.activeApp(this.deviceInfo.getDeviceId(), appPackage, remoteAddress, deviceInfo.getPlatform());
log.debug("设备【{}】完成启动App....................", this.deviceInfo.getDeviceId());
} finally {
if (isRecord && null != watchAppActive) {
watchAppActive.stop();
long time = watchAppActive.getTime(TimeUnit.MILLISECONDS);
@ -523,8 +527,6 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
devicePerInfo.getAppPerInfo().setAppActiveTime(time);
}
}
}
}
private void pressHome() {
@ -576,8 +578,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
removeApp = driver.removeApp(appPackage);
}
return removeApp;
} else { //ios
return IosUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage);
} else { //ios和harmony
return PhoneUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform());
}
}
@ -604,8 +606,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
appInstalled = driver.isAppInstalled(appPackage);
}
return appInstalled;
} else { //ios
return IosUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage);
} else {
return PhoneUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform());
}
}
@ -621,10 +623,9 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
log.warn("启动app时前停止。。。。。。。。。。");
throw new ExecuteException("执行停止");
}
if ("0".equals(deviceInfo.getPlatform())) {
return AndroidUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage);
}
return true;
boolean success = PhoneUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage, deviceInfo.getPlatform());
log.debug("设备【{}】清除数据结果:{}",deviceId,success);
return success;
}
@ -673,31 +674,11 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
throw new ExecuteException("执行停止");
}
try {
boolean isOk = false;
boolean isIOS = this.deviceDriver instanceof IOSDriver;
boolean isAndroid = this.deviceDriver instanceof AndroidDriver;
String platform = null;
if (null != deviceInfo) {
platform = deviceInfo.getPlatform();
}
if (null == platform) {
if (isAndroid) {
platform = "0";
} else if (isIOS) {
platform = "1";
}
log.debug("设备信息未获取到平台,直接根据驱动判断平台(1-IOS 0-安卓):{}", platform);
}
boolean success = false;
try {
if (isAndroid) {
isOk = AndroidUtil.installApp(this.remoteAddress, deviceId, appPath, platform, packageName);
} else if (isIOS) {
isOk = IosUtil.installApp(this.remoteAddress, deviceId, appPath, platform);
} else {
log.warn("driver类型未知安装应用失败");
}
success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform());
long t3 = System.currentTimeMillis();
log.debug("单独安装应用耗时:{}", (t3 - t2) / 1000);
log.debug("单独安装应用耗时:{},结果:{}", (t3 - t2) / 1000,success);
} catch (Exception e) {
log.warn("设备【{}】安装应用【{}】出现异常,重试一次。。。", deviceId, packageName, e);
try {
@ -717,15 +698,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
t2 = System.currentTimeMillis();
log.debug("单独下载应用耗时:{}", (t2 - t1) / 1000);
log.info("download app finish,start install ............");
if (isAndroid) {
// 回到桌面
pressHome();
isOk = AndroidUtil.installApp(this.remoteAddress, deviceId, appPath, platform, packageName);
} else if (isIOS) {
isOk = IosUtil.installApp(this.remoteAddress, deviceId, appPath, platform);
} else {
log.warn("driver类型未知安装应用失败");
}
success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform());
}
// if (this.deviceDriver instanceof AndroidDriver) {
// AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) this.deviceDriver;
@ -770,11 +743,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
@Override
public void startRecord() {
if ("0".equals(deviceInfo.getPlatform())) {
AndroidUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"),deviceInfo.getOtherInfo().get("tenantId"));
} else {
IosUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"), deviceInfo.getOtherInfo().get("tenantId"),deviceInfo.getResolution());
}
PhoneUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"),deviceInfo.getOtherInfo().get("tenantId"),deviceInfo.getResolution(),deviceInfo.getPlatform());
}
@Override
@ -786,11 +755,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
if (ObjectUtil.isNull(initData.get("save"))) {
throw new RuntimeException("视频保存标识不能为空");
}
if ("0".equals(deviceInfo.getPlatform())) {
result = AndroidUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData);
} else {
result = IosUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData);
}
result = PhoneUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData,deviceInfo.getPlatform());
log.debug("任务{},得到录频保存地址:{}",initData.get("taskId"),result);
return result;
}
@ -862,7 +828,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
if (this.deviceDriver instanceof MobileDriver) {
log.debug("设备【{}】采取自研自动化重新启动App....................",this.deviceInfo.getDeviceId());
String uuid = this.deviceInfo.getDeviceId();
IosUtil.terminateApp(uuid, appPackage, remoteAddress);
PhoneUtil.terminateApp(uuid, appPackage, remoteAddress,deviceInfo.getPlatform());
} else {
log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId());
AppiumDriver deviceDriver = (AppiumDriver) this.deviceDriver;
@ -881,7 +847,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
}
} else if (deviceDriver instanceof IOSDriver) {
String uuid = (String) deviceDriver.getCapabilities().getCapability(UDID);
IosUtil.terminateApp(uuid, appPackage, remoteAddress);
IosUtil.terminateApp(uuid, appPackage, remoteAddress,deviceInfo.getPlatform());
}
}

View File

@ -42,6 +42,7 @@ public class DeviceConnectionManager implements DeviceConnectionService {
ResponseEntity<Map> resEntity = restTemplate.getForEntity(url, Map.class);
Map resultBody = resEntity.getBody();
DeviceInfo deviceInfo = new DeviceInfo();
log.debug("查询得到的设备信息:{}", JSON.toJSONString(resultBody));
if (resultBody != null && (boolean)resultBody.get("success") && resultBody.get("data") != null) { // 请求成功,获取到设备信息
Map data = (Map) resultBody.get("data");
log.info("设备【token-{}】请求结果:{}", deviceToken, JsonUtils.toJson(data));
@ -64,6 +65,8 @@ public class DeviceConnectionManager implements DeviceConnectionService {
Object forwardPort = deviceMap.get("forwardPort");
log.debug("拿到设备【{}】在上位机用于adb转发的端口{}",deviceInfo.getDeviceId(),forwardPort);
deviceInfo.setForwardPort(Integer.parseInt(forwardPort+""));
}else if ("2".equalsIgnoreCase(deviceInfo.getPlatform())) { //harmony
log.debug("该设备【{}】是harmony设备",deviceInfo.getDeviceId());
}
} else { // pc设备
deviceInfo.setMobile(false);

View File

@ -10,19 +10,18 @@ import org.springframework.http.HttpEntity;
import java.util.Map;
public class AndroidUtil {
public class AndroidUtil extends PhoneUtil{
private static final Logger logger = LoggerFactory.getLogger(AndroidUtil.class);
private static final String CLEAN_APP_DATA_URL = "http://%s/engine/cleanData";
private static final String GET_PACKAGE_VERSION_URL = "http://%s/engine/getPackageVersion";
private static final String ALL_SCREEN_SHOT_URL = "http://%s/engine/uploadShotAllScreen";
private static final String START_RECORD_URL = "http://%s/engine/startRecord";
private static final String END_RECORD_URL = "http://%s/engine/endRecord";
private static final String INSTALL_APP = "http://%s/engine/installApp";
private static final String ACTIVE_APP = "http://%s/engine/activeApp";
private static final String RELEASE_ADB_FORWARD_PORT = "http://%s/engine/releaseAdbForwardPort";
public static boolean releaseAdbForwardPort(String remoteAddress,String deviceId){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
@ -35,18 +34,6 @@ public class AndroidUtil {
}
}
public static boolean cleanAppData(String remoteAddress,String deviceId,String appPackage){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
return HttpUtils.doPost(String.format(CLEAN_APP_DATA_URL, remoteAddress),entity, Boolean.class);
} catch (Exception e) {
logger.error("清除数据失败,原因:{}",e);
return false;
}
}
/**
*
@ -68,54 +55,13 @@ public class AndroidUtil {
}
}
public static String startRecord(String remoteAddress,String deviceId,String taskId,String tenantId){
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setPlatform("0");
info.setTenantId(tenantId);
logger.info("移动任务开启录屏任务id:{} tenantId{}", taskId, tenantId);
info.setTaskId(taskId);
logger.info("录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(START_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("开启录屏失败",e);
}
return result;
}
public static String stopRecord(String remoteAddress, String deviceId,Map<String, Object> initData) {
String tenantId = (String)initData.get("tenantId");
Boolean save = (Boolean) initData.get("save");
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
String taskId = (String)initData.get("taskId");
logger.info("移动任务结束录屏任务id:"+taskId);
info.setTaskId(taskId);
info.setTenantId(tenantId);
info.setSave(save);
logger.info("结束录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(END_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("移动任务结束录屏失败",e);
}
return result;
}
public static String snapshotAllScreen(String remoteAddress, DeviceInfo deviceInfo, String tenantId, String taskId) {
public static String snapshotAllScreen(String remoteAddress, DeviceInfo deviceInfo, String tenantId) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setPlatform(deviceInfo.getPlatform());
info.setDeviceId(deviceInfo.getDeviceId());
info.setResolution(deviceInfo.getResolution());
info.setTenantId(tenantId); //租户id
info.setTaskId(taskId);
logger.info("参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
@ -127,44 +73,4 @@ public class AndroidUtil {
return result;
}
public static boolean installApp(String remoteAddress, String deviceId, String appPath,String platform, String packageName) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPath(appPath);
info.setPlatform(platform);
info.setAppPackage(packageName);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = HttpUtils.doInstall(String.format(INSTALL_APP, remoteAddress), entity);
if (!isOk) {
logger.warn("设备【{}】安装应用失败。。。。", deviceId);
throw new ExecuteException("安装App出错");
}
return true;
}
public static boolean activeApp(String uuid, String appPackage, String remoteAddress) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(uuid);
info.setAppPackage(appPackage);
info.setPlatform("0");
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = false;
try {
isOk = HttpUtils.doPost(String.format(ACTIVE_APP, remoteAddress), entity, Boolean.class);
} catch (Exception e) {
logger.error("设备【{}】启动应用失败", uuid,e);
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
logger.warn("设备【{}】关闭app前停止。。。。。。。。。。", uuid);
throw new ExecuteException("执行停止");
}
isOk = HttpUtils.doPost(String.format(ACTIVE_APP, remoteAddress), entity, Boolean.class);
}
logger.debug("启动应用结果:{}", isOk);
if (!isOk) {
throw new ExecuteException("启动应用失败");
}
return true;
}
}

View File

@ -0,0 +1,4 @@
package net.northking.cctp.se.util;
public class HarmonyUtil extends PhoneUtil{
}

View File

@ -2,162 +2,59 @@ package net.northking.cctp.se.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.AppiumDriver;
import net.northking.cctp.element.core.exception.EnvironmentException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.se.device.bean.DebuggerDeviceInfo;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import java.net.URI;
import java.util.Map;
public class IosUtil {
public class IosUtil extends PhoneUtil{
private static final Logger logger = LoggerFactory.getLogger(IosUtil.class);
private static final String IS_APP_INSTALLED = "http://%s/engine/isAppInstalled";
private static final String REMOVE_APP = "http://%s/engine/removeApp";
private static final String INSTALL_APP = "http://%s/engine/installApp";
private static final String ACTIVE_APP = "http://%s/engine/activeApp";
private static final String TERMINATE_APP = "http://%s/engine/terminateApp";
private static final String ALL_SCREEN_SHOT_URL = "http://%s/engine/uploadShotAllScreen";
private static final String START_RECORD_URL = "http://%s/engine/startRecord";
private static final String END_RECORD_URL = "http://%s/engine/endRecord";
public static boolean isAppInstalled(String remoteAddress, String deviceId, String appPackage) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
public static Double getWindowsSize(AppiumDriver<WebElement> driver) {
if (null == driver) {
logger.error("driver获取为空");
throw new EnvironmentException("设备自动化驱动连接已断开,请检查驱动程序是否正常");
}
Capabilities capabilities = driver.getCapabilities();
//todo:这里后面确定wda的地址是多少
String wdaUrl = (String) capabilities.getCapability("webDriverAgentUrl");
String deviceId = (String) capabilities.getCapability("deviceName");
String windowsUrl = wdaUrl + "/window/size";
Double scale = 2.0;
try {
return HttpUtils.doPost(String.format(IS_APP_INSTALLED, remoteAddress), entity, Boolean.class);
JSONObject object = HttpUtils.doGet(new URI(windowsUrl), JSONObject.class);
JSONObject value = object.getJSONObject("value");
scale = value.getDoubleValue("scale");
logger.info("手机【{}】的宽高比例:{}",deviceId,scale);
return scale;
} catch (Exception e) {
logger.error("设备【{}】查询app是否安装出错", deviceId, e);
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
logger.warn("设备【{}】判断是否安装app前停止。。。。。。。。。。", deviceId);
throw new ExecuteException("执行停止");
}
return HttpUtils.doPost(String.format(IS_APP_INSTALLED, remoteAddress), entity, Boolean.class);
logger.error("获取手机【{}】宽高比例失败:{}",deviceId, e);
throw new ExecuteException("获取手机宽高比例失败");
}
}
public static boolean removeApp(String remoteAddress, String deviceId, String appPackage) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
try {
return HttpUtils.doPost(String.format(REMOVE_APP, remoteAddress), entity, Boolean.class);
} catch (Exception e) {
logger.error("卸载应用出错", e);
throw new ExecuteException("卸载设备上App出错");
}
}
public static boolean installApp(String remoteAddress, String deviceId, String appPath,String platform) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPath(appPath);
info.setPlatform(platform);
logger.debug("ios install info:{}",JSONObject.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = HttpUtils.doInstall(String.format(INSTALL_APP, remoteAddress), entity);
if (!isOk) {
logger.warn("设备【{}】安装应用失败。。。。", deviceId);
throw new ExecuteException("安装App出错");
}
return true;
}
public static boolean activeApp(String uuid, String appPackage, String remoteAddress) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(uuid);
info.setAppPackage(appPackage);
info.setPlatform("1");
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = false;
try {
isOk = HttpUtils.doPost(String.format(ACTIVE_APP, remoteAddress), entity, Boolean.class);
} catch (Exception e) {
logger.error("设备【{}】启动应用失败", uuid,e);
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
logger.warn("设备【{}】关闭app前停止。。。。。。。。。。", uuid);
throw new ExecuteException("执行停止");
}
isOk = HttpUtils.doPost(String.format(ACTIVE_APP, remoteAddress), entity, Boolean.class);
}
logger.debug("启动应用结果:{}", isOk);
if (!isOk) {
throw new ExecuteException("启动应用失败");
}
return true;
}
public static boolean terminateApp(String uuid, String appPackage, String remoteAddress) {
Boolean isOk = false;
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(uuid);
info.setAppPackage(appPackage);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
try {
isOk = HttpUtils.doPost(String.format(TERMINATE_APP, remoteAddress), entity, Boolean.class);
} catch (Exception e) {
logger.error("设备【{}】关闭应用失败,重试一次", uuid,e);
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
logger.warn("设备【{}】关闭app前停止。。。。。。。。。。", uuid);
throw new ExecuteException("执行停止");
}
isOk = HttpUtils.doPost(String.format(TERMINATE_APP, remoteAddress), entity, Boolean.class);
}
logger.debug("关闭应用结果:{}", isOk);
if (!isOk) {
// throw new ExecuteException("关闭应用失败");
}
return true;
}
public static String startRecord(String remoteAddress, String deviceId, String taskId, String tenantId,String resolution) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setPlatform("1");
info.setTenantId(tenantId);
logger.info("移动任务开启录屏任务id:{} tenantId{}", taskId, tenantId);
info.setTaskId(taskId);
info.setResolution(resolution);
logger.info("录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(START_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("开启录屏失败", e);
}
return result;
}
public static String stopRecord(String remoteAddress, String deviceId, Map<String, Object> initData) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
Boolean save = (Boolean) initData.get("save");
info.setDeviceId(deviceId);
info.setPlatform("1");
logger.info("移动任务结束录屏任务id:" + initData.get("taskId"));
info.setTaskId(initData.get("taskId").toString());
info.setTenantId(initData.get("tenantId").toString());
info.setSave(save);
logger.info("结束录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(END_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("结束录屏失败", e);
}
return result;
}
}

View File

@ -0,0 +1,172 @@
package net.northking.cctp.se.util;
import com.alibaba.fastjson.JSON;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.se.device.bean.DebuggerDeviceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import java.util.Map;
public class PhoneUtil {
private static final Logger logger = LoggerFactory.getLogger(PhoneUtil.class);
private static final String CLEAN_APP_DATA_URL = "http://%s/engine/cleanData";
private static final String ACTIVE_APP = "http://%s/engine/activeApp";
private static final String IS_APP_INSTALLED = "http://%s/engine/isAppInstalled";
private static final String TERMINATE_APP = "http://%s/engine/terminateApp";
private static final String REMOVE_APP = "http://%s/engine/removeApp";
private static final String INSTALL_APP = "http://%s/engine/installApp";
private static final String START_RECORD_URL = "http://%s/engine/startRecord";
private static final String END_RECORD_URL = "http://%s/engine/endRecord";
public static boolean cleanAppData(String remoteAddress,String deviceId,String appPackage,String platform){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
return HttpUtils.doPost(String.format(CLEAN_APP_DATA_URL, remoteAddress),entity, Boolean.class);
} catch (Exception e) {
logger.error("清除数据失败,原因:{}",e);
return false;
}
}
public static boolean activeApp(String uuid, String appPackage,String remoteAddress,String platform){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(uuid);
info.setAppPackage(appPackage);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = HttpUtils.doInstall(String.format(ACTIVE_APP, remoteAddress), entity);
logger.debug("启动应用【{}】结果:{}", appPackage,isOk);
if (!isOk) {
throw new ExecuteException("启动应用失败");
}
return true;
} catch (Exception e) {
logger.error("设备【{}】启动应用失败",uuid,e);
throw new ExecuteException("启动应用失败");
}
}
public static boolean isAppInstalled(String remoteAddress, String deviceId, String appPackage,String platform) {
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
return HttpUtils.doPost(String.format(IS_APP_INSTALLED, remoteAddress),entity, Boolean.class);
} catch (Exception e) {
logger.error("查询app是否安装出错",e);
throw new ExecuteException("查询设备上App是否安装出错");
}
}
public static boolean terminateApp(String uuid, String appPackage, String remoteAddress,String platform) {
Boolean isOk = false;
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(uuid);
info.setAppPackage(appPackage);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
try {
isOk = HttpUtils.doPost(String.format(TERMINATE_APP, remoteAddress), entity, Boolean.class);
} catch (Exception e) {
logger.error("设备【{}】关闭应用失败,重试一次", uuid,e);
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
logger.warn("设备【{}】关闭app前停止。。。。。。。。。。", uuid);
throw new ExecuteException("执行停止");
}
isOk = HttpUtils.doPost(String.format(TERMINATE_APP, remoteAddress), entity, Boolean.class);
}
logger.debug("关闭应用结果:{}", isOk);
if (!isOk) {
// throw new ExecuteException("关闭应用失败");
}
return true;
}
public static boolean removeApp(String remoteAddress, String deviceId,String appPackage,String platform){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPackage(appPackage);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
return HttpUtils.doPost(String.format(REMOVE_APP, remoteAddress),entity, Boolean.class);
} catch (Exception e) {
logger.error("卸载应用出错",e);
throw new ExecuteException("卸载设备上App出错");
}
}
public static boolean installApp(String remoteAddress,String deviceId,String appPath,String platform){
try {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setAppPath(appPath);
info.setPlatform(platform);
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
Boolean isOk = HttpUtils.doInstall(String.format(INSTALL_APP, remoteAddress), entity);
if (!isOk) {
throw new ExecuteException("安装App出错");
}
return true;
} catch (Exception e) {
logger.error("安装应用出错",e);
throw new ExecuteException("安装App出错");
}
}
public static String startRecord(String remoteAddress, String deviceId, String taskId,String tenantId,String resolution,String platform) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setPlatform(platform);
info.setTaskId(taskId);
info.setTenantId(tenantId);
info.setResolution(resolution);
logger.info("录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(START_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("开启录屏失败",e);
}
return result;
}
public static String stopRecord(String remoteAddress, String deviceId, Map<String, Object> initData,String platform) {
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
info.setDeviceId(deviceId);
info.setPlatform(platform);
info.setTaskId(initData.get("taskId").toString());
info.setTenantId(initData.get("tenantId").toString());
logger.info("结束录屏参数:{}", JSON.toJSONString(info));
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
String result = null;
try {
result = HttpUtils.doPost(String.format(END_RECORD_URL, remoteAddress), entity, String.class);
} catch (Exception e) {
logger.error("结束录屏失败",e);
}
return result;
}
}

View File

@ -1356,6 +1356,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
private AtuPlanScriptResultDto queryScriptData(List<String> scriptList,List<AtuPlanScriptLinkDto> result) {
AtuPlanScriptResultDto resultDto = new AtuPlanScriptResultDto();
ResultWrapper<AtuScriptInfoResultDto> wrapper = scriptCaseFeignClient.checkScriptData(scriptList);
logger.debug("拿到的脚本数据:{}",JSON.toJSONString(wrapper.getData()));
//脚本名称
Map<String, String> scriptMap = wrapper.getData().getScriptMap();
//校验结果
@ -3133,7 +3134,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
List<EntrustDeviceDto> mobileTypeList = mobDeviceList.stream()
.filter(entrustDeviceDto -> deviceList.contains(entrustDeviceDto.getDeviceId()))
.collect(Collectors.toList());
logger.debug("设备数量为:{}", mobileTypeList.size());
logger.debug("移动端平台:{},委托的设备为:{}", type,JSON.toJSONString(mobileTypeList));
sendEntrustMsgToQueue(entrustMsg, engineInfoListDto.getEngineInfoList(),
PlanConstant.ENGINE_TYPE_MOBILE, queueName, mobileTypeList);
});
@ -3252,6 +3253,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
boolean androidDevice = false;
boolean iosDevice = false;
boolean harmonyDevice = false;
boolean pcDevice = false;
for (DeviceList device : feignDto.getDeviceLists()) {
if (PlanConstant.DEVICE_TYPE_PC.equals(device.getDeviceType())){
@ -3265,6 +3267,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
case PlanConstant.PLATFORM_TYPE_IOS:
iosDevice = true;
break;
case PlanConstant.PLATFORM_TYPE_HARMONY:
harmonyDevice = true;
break;
default:
break;
}
@ -3273,6 +3278,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
//用例类型
boolean hasAndroid = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID);
boolean hasIOS = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_IOS);
boolean hasHarmony = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_HARMONY);
boolean hasPC = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_PC);
boolean hasMob = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_MOB);
StringBuilder deviceType = new StringBuilder();
@ -3284,6 +3290,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
if (hasIOS && !iosDevice) {
deviceType.append(MsgConstant.ERROR_PLATFORM_TYPE_IOS);
}
if (hasHarmony && !harmonyDevice) {
deviceType.append("【Harmony】");
}
}
//PC端的校验
if (hasPC && !pcDevice) {

View File

@ -133,6 +133,7 @@ public class MsgConstant {
//
public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_IOS= "hasIOS";
public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID= "hasAndroid";
public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_HARMONY= "hasHarmony";
public static final String PLATFORM_TYPE_HAS_PC= "hasPC";
public static final String PLATFORM_TYPE_HAS_MOB= "hasMob";
public static final String MAP_JSON_APPALIVE= "appAlive";

View File

@ -33,6 +33,17 @@ public class AtuPlanScriptResultDto {
@ApiModelProperty("是否存在接口脚本")
private boolean hasAPI;
@ApiModelProperty("是否存在harmony脚本")
private boolean hasHarmony;
public boolean isHasHarmony() {
return hasHarmony;
}
public void setHasHarmony(boolean hasHarmony) {
this.hasHarmony = hasHarmony;
}
public List<AtuPlanScriptLinkDto> getSimpleList() {
return simpleList;
}

View File

@ -31,6 +31,10 @@ public class AtuScriptInfoResultDto {
private boolean hasAPI = false;
;
@ApiModelProperty("是否存在harmony脚本")
private boolean hasHarmony = false;
;
@ApiModelProperty("脚本Map")
private Map<String, String> scriptMap;
@ -97,4 +101,12 @@ public class AtuScriptInfoResultDto {
public void setHasAPI(boolean hasAPI) {
this.hasAPI = hasAPI;
}
public boolean isHasHarmony() {
return hasHarmony;
}
public void setHasHarmony(boolean hasHarmony) {
this.hasHarmony = hasHarmony;
}
}

View File

@ -69,7 +69,7 @@ public class FileMinioServiceImpl implements FileService {
try {
apkFile = new ApkFile(file);
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(ScriptCaseAppError.APK_READ_ERR);
}
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
@ -77,7 +77,7 @@ public class FileMinioServiceImpl implements FileService {
try {
apkMeta = apkFile.getApkMeta();
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
}
result.put(ScriptConstant.APP_NAME, apkMeta.getName());
@ -87,7 +87,7 @@ public class FileMinioServiceImpl implements FileService {
try {
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
}
//封装activity
@ -95,7 +95,7 @@ public class FileMinioServiceImpl implements FileService {
try {
activity = getActtivity(apkFile);
} catch (Exception e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
}
result.put(ScriptConstant.ACTIVITY, activity);
@ -113,7 +113,7 @@ public class FileMinioServiceImpl implements FileService {
fileMessage.setMessage(result);
fileMessage.setManifest(manifest);
} catch (Exception e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL);
} finally {
try {
@ -121,7 +121,7 @@ public class FileMinioServiceImpl implements FileService {
apkFile.close();
}
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
}
}
@ -132,9 +132,9 @@ public class FileMinioServiceImpl implements FileService {
try {
long t1 = System.currentTimeMillis();
ServletOutputStream out = response.getOutputStream();
simpleStorageService.getFileAndDirectResponseToHttp(out,result[1]);
simpleStorageService.getFileAndDirectResponseToHttp(out, result[1]);
long t2 = System.currentTimeMillis();
logger.debug("单独从服务器下载应用文件耗时:{}",(t2-t1)/1000);
logger.debug("单独从服务器下载应用文件耗时:{}", (t2 - t1) / 1000);
} catch (Exception e) {
logger.error("文件下载流异常", e);
}
@ -163,7 +163,7 @@ public class FileMinioServiceImpl implements FileService {
long t1 = System.currentTimeMillis();
downLoadAndReturn(response, result);
long t2 = System.currentTimeMillis();
logger.debug("下载应用文件完整耗时:{}",(t2-t1)/1000);
logger.debug("下载应用文件完整耗时:{}", (t2 - t1) / 1000);
}
@ -209,7 +209,7 @@ public class FileMinioServiceImpl implements FileService {
try {
requestUrl = new URL(urls[i]);
} catch (MalformedURLException e) {
logger.error("",e);
logger.error("", e);
}
try {
urlCon = (HttpURLConnection) requestUrl.openConnection();
@ -219,7 +219,7 @@ public class FileMinioServiceImpl implements FileService {
inputStreams[i] = urlCon.getInputStream();
}
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
} finally {
if (urlCon == null) {
urlCon.disconnect();
@ -278,7 +278,7 @@ public class FileMinioServiceImpl implements FileService {
Map<String, Object> result = new HashMap<>();
// 解析应用
ApkFile apkFile = null;
File file=null;
File file = null;
try {
String[] fileNames = MinioPathUtils.idToPath(appUrl);
file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
@ -290,7 +290,7 @@ public class FileMinioServiceImpl implements FileService {
try {
apkFile = new ApkFile(file);
} catch (IOException e) {
logger.info("new ApkFile(file)解析APK异常信息{}",e.getMessage());
logger.info("new ApkFile(file)解析APK异常信息{}", e.getMessage());
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
}
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
@ -298,7 +298,7 @@ public class FileMinioServiceImpl implements FileService {
try {
apkMeta = apkFile.getApkMeta();
} catch (IOException e) {
logger.info("apkFile.getApkMeta()解析APK异常信息{}",e.getMessage());
logger.info("apkFile.getApkMeta()解析APK异常信息{}", e.getMessage());
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
}
result.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
@ -310,7 +310,7 @@ public class FileMinioServiceImpl implements FileService {
try {
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
}
// 封装activity
@ -318,7 +318,7 @@ public class FileMinioServiceImpl implements FileService {
try {
activity = getActtivity(apkFile);
} catch (Exception e) {
logger.info("getActtivity(apkFile)解析APK异常信息{}",e.getMessage());
logger.info("getActtivity(apkFile)解析APK异常信息{}", e.getMessage());
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
}
result.put(ScriptConstant.ACTIVITY, activity);
@ -340,20 +340,20 @@ public class FileMinioServiceImpl implements FileService {
} catch (PlatformRuntimeException e) {
throw e;
} catch (FileDownloadException e) {
logger.error("",e);
logger.error("", e);
} finally {
try {
if (null != apkFile) {
apkFile.close();
}
if(file!=null){
if (file != null) {
boolean delete = file.delete();
if (!delete) {
logger.error("删除文件失败");
}
}
} catch (IOException e) {
logger.error("",e);
logger.error("", e);
}
}
return fileMessage;
@ -364,7 +364,7 @@ public class FileMinioServiceImpl implements FileService {
FileMessage fileMessage = new FileMessage();
Map<String, Object> map = new HashMap<>();
long size = 0;
File file =null;
File file = null;
//解析应用
try {
String[] fileNames = MinioPathUtils.idToPath(appUrl);
@ -378,7 +378,7 @@ public class FileMinioServiceImpl implements FileService {
} catch (PlatformRuntimeException e) {
throw e;
} catch (Exception e) {
logger.error("",e);
logger.error("", e);
throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL);
}
if (map.get(ScriptConstant.STATUS_ERR) != null) {
@ -399,8 +399,8 @@ public class FileMinioServiceImpl implements FileService {
if (!delete) {
logger.error("删除文件失败");
}
}catch (Exception e){
logger.error("",e);
} catch (Exception e) {
logger.error("", e);
}
return fileMessage;
}
@ -409,12 +409,7 @@ public class FileMinioServiceImpl implements FileService {
public FileMessage uploadHarmonyOSLargeApp(String appUrl) {
FileMessage fileMessage = new FileMessage();
long size = 0;
File file=null;
InputStream inputStream = null;
ZipInputStream zin = null;
ZipFile zf = null;
FileInputStream fileInputStream = null;
File apkFile = null;
File file = null;
//解析应用
try {
String[] fileNames = MinioPathUtils.idToPath(appUrl);
@ -424,107 +419,45 @@ public class FileMinioServiceImpl implements FileService {
logger.info("hap文件为空");
throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
}
//apk文件流
InputStream apkIn = null;
//apk文件名
String apkFileName = null;
//hapConfig
String hapConfig = "";
//设置编码
Charset charset = Charset.forName(ScriptConstant.CHARSET_FOR_NAME);
try {
// hap包放进输入流
fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
logger.error("",e);
Hap hap = new Hap(file);
InputStream appIconInputStream = hap.getAppIcon(); //应用图片
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int length = 0;
while ((length = appIconInputStream.read(data, 0, data.length)) > 0) {
byteArrayOutputStream.write(data, 0, length);
}
inputStream = new BufferedInputStream(fileInputStream);
// 压缩包输入流
zin = new ZipInputStream(inputStream, charset);
// 压缩包文件
zf = new ZipFile(file);
String appName = hap.getAppName();
String versionName = hap.getVersionName();
long versionCode = hap.getVersionCode();
String packageName = hap.getHapModule().getApp().getBundleName();
Map<String, Object> result = new HashMap<>();
//中文名
result.put("appName", appName);
//包名
result.put("packageName", packageName);
//版本
result.put("buildVersion", versionName);
//平台
result.put("platform", PlatformType.getCode("harmonyos"));
//app大小
result.put("appSize", FileMinioServiceImpl.getAppSize(size));
//下载地址
result.put("appUrl", appUrl);
//图标
result.put("appIcon", byteArrayOutputStream.toByteArray());
//获取config以及apk文件
ZipEntry ze;
while ((ze = zin.getNextEntry()) != null) {
//遍历的当前文件名
String fileName = ze.getName();
//当遍历到config.json
if (ScriptConstant.CONFIG_JSON_NAME.equals(fileName)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(zf.getInputStream(ze)));
String line;
while ((line = reader.readLine()) != null) {
hapConfig += line;
}
if (reader != null) {
reader.close();
}
}
//当遍历到apk文件
if (ScriptConstant.FILE_ENDWITH_APK.equals(fileName.substring(fileName.length() - 4))) {
apkIn = zf.getInputStream(ze);
apkFileName = fileName;
}
}
// 解析
// 将config.json转成map
JSONObject apkConfigJsonObject = JSON.parseObject(hapConfig);
fileMessage.setMessage(result);
// 将apk流转化为文件
apkFile = new File(apkFileName);
FileUtils.copyInputStreamToFile(apkIn, apkFile);
//解析apk得到信息
fileMessage = resolveApk(apkFile);
Map<String, Object> message = fileMessage.getMessage();
message.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
message.put(ScriptConstant.PLATFORM, PlatformType.getCode(ScriptConstant.PLATFORM_HARMONY));
message.put(ScriptConstant.APP_URL, appUrl);
fileMessage.setFileSaveUrl(appUrl);
fileMessage.setFileUrl(appUrl);
fileMessage.setMessage(message);
} catch (PlatformRuntimeException e) {
throw e;
} catch (Exception e) {
logger.error("",e);
throw new PlatformRuntimeException(ScriptCaseAppError.HAP_ERR);
}finally {
if (zf != null) {
try {
zf.close();
} catch (IOException e) {
logger.error("",e);
}
}
if (zin != null) {
try {
zin.close();
} catch (IOException e) {
logger.error("",e);
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
logger.error("",e);
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
logger.error("",e);
}
}
if (apkFile != null && apkFile.exists()) {
boolean delete = apkFile.delete();
if (!delete) {
logger.error("删除文件失败");
}
}
logger.error("", e);
throw new PlatformRuntimeException(new ErrorMessage("hap解析失败"));
} finally {
if (file != null) {
boolean delete = file.delete();
if (!delete) {
@ -534,4 +467,59 @@ public class FileMinioServiceImpl implements FileService {
}
return fileMessage;
}
}
private FileMessage resolveHapFile(JSONObject hapModuleJson) {
FileMessage fileMessage = new FileMessage();
Map<String, Object> result = new HashMap<>();
try {
//中文名
result.put("appName", "暂无");
//包名
result.put("packageName", hapModuleJson.getJSONObject("app").getString("bundleName"));
result.put("buildVersion", hapModuleJson.getJSONObject("app").getString("versionName"));
result.put("platform", PlatformType.getCode("harmonyos"));
//图标
// try {
// result.put("appIcon", apkFile.getAllIcons().get(0).getData());
// } catch (IOException e) {
// logger.error("",e);
// throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
// }
//封装activity
// String activity = null;
// try {
// activity = getActtivity(apkFile);
// } catch (Exception e) {
// logger.error("",e);
// throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
// }
// result.put("activity", activity);
Map<String, Object> manifest = new HashMap<>(8);
// manifest.put("package", apkMeta.getPackageName());
// Map<String, Object> application = new HashMap<>(8);
// List<Map<String, String>> launcherActivities = new ArrayList<>();
// Map<String, String> params = new HashMap<>(8);
// params.put("name", activity);
// launcherActivities.add(params);
// application.put("launcherActivities", launcherActivities);
// manifest.put("application", application);
fileMessage.setMessage(result);
fileMessage.setManifest(manifest);
} catch (Exception e) {
logger.error("", e);
throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL);
} finally {
// try {
// if (null != apkFile) {
// apkFile.close();
// }
// } catch (IOException e) {
// logger.error("",e);
// }
}
return fileMessage;
}
}

View File

@ -1588,6 +1588,7 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
AtuScriptInfoResultDto checkDto = checkScriptType(resultQuery);
map.put(ScriptConstant.HAS_AND,checkDto.isHasAndroid());
map.put(ScriptConstant.HAS_IOS,checkDto.isHasIOS());
map.put(ScriptConstant.HAS_HAR,checkDto.isHasHarmony());
map.put(ScriptConstant.HAS_PC,checkDto.isHasPC());
map.put(ScriptConstant.HAS_MOB,checkDto.isHasMob());
@ -2182,6 +2183,8 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
boolean hasAndroidScript = false;
//是否含有IOS用例
boolean hasIOSScript = false;
//是否含有harmony用例
boolean hasHarmonyScript = false;
//是否含有B/S用例
boolean hasBSScript = false;
//是否含有C/S用例
@ -2211,6 +2214,10 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
hasMobScript = true;
hasIOSScript = true;
break;
case ScriptConstant.STR_SIX:
hasMobScript = true;
hasHarmonyScript = true;
break;
}
}
@ -2222,6 +2229,7 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
dto.setHasAPI(hasAPIScript);
dto.setHasBS(hasBSScript);
dto.setHasCS(hasCSScript);
dto.setHasHarmony(hasHarmonyScript);
return dto;
}

View File

@ -210,6 +210,7 @@ public class ScriptConstant {
public static final String CASE_NUM = "caseNum";
public static final String HAS_AND = "hasAndroid";
public static final String HAS_IOS = "hasIOS";
public static final String HAS_HAR = "hasHarmony";
public static final String HAS_PC = "hasPC";
public static final String HAS_MOB = "hasMob";
public static final String APP_ALIVE = "appAlive";

View File

@ -31,6 +31,10 @@ public class AtuScriptInfoResultDto {
private boolean hasAPI = false;
;
@ApiModelProperty("是否存在Harmony脚本")
private boolean hasHarmony = false;
;
@ApiModelProperty("脚本Map")
private Map<String, String> scriptMap;
@ -95,6 +99,15 @@ public class AtuScriptInfoResultDto {
}
public void setHasAPI(boolean hasAPI) {
this.hasAPI = hasAPI;
}
public boolean isHasHarmony() {
return hasHarmony;
}
public void setHasHarmony(boolean hasHarmony) {
this.hasHarmony = hasHarmony;
}
}

View File

@ -0,0 +1,99 @@
package net.northking.cctp.scriptcase.tools;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
public class ByteUtils {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
public static String byteToHex(byte b) {
byte[] hexChars = new byte[2];
int v = b & 0xFF;
hexChars[0] = HEX_ARRAY[v >>> 4];
hexChars[1] = HEX_ARRAY[v & 0x0F];
return new String(hexChars, StandardCharsets.UTF_8);
}
public static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int v = bytes[i] & 0xFF;
hexChars[i * 2] = HEX_ARRAY[v >>> 4];
hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
public static String bytesToIp(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
if (sb.length() > 0) {
sb.append(".");
}
if (bytes.length > 4) {
sb.append(byteToHex(b));
} else {
sb.append(b & (byte) 0xFF);
}
}
return sb.toString();
}
public static byte[] hexStringToByteArray(String hexString) {
if (hexString == null) return new byte[0];
String fixString = hexString.trim().replace(" ", "");
if (fixString.length() < 2) return new byte[0];
byte[] byteArray = new byte[fixString.length() / 2];
try {
for (int i = 0; i < byteArray.length; i++) {
int index = i * 2;
int b = Integer.parseInt(fixString.substring(index, index + 2), 16);
byteArray[i] = (byte) b;
}
} catch (NumberFormatException ignore) {
}
return byteArray;
}
/**
* java int
*
* @param bytes
* @return
*/
public static int littleEndianByteArrayToInt(byte[] bytes) {
if (bytes.length < 4) {
throw new NumberFormatException("参数长度应大于等于4实际为" + bytes.length);
}
byte tmp = bytes[0];
bytes[0] = bytes[3];
bytes[3] = tmp;
tmp = bytes[1];
bytes[1] = bytes[2];
bytes[2] = tmp;
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getInt();
}
public static byte[] intToLittleEndianByteArray(int number) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(number);
return buffer.array();
}
public static short littleEndianByteArrayToShort(byte[] bytes) {
if (bytes.length < 2) {
throw new NumberFormatException("参数长度应大于等于4实际为" + bytes.length);
}
byte tmp = bytes[0];
bytes[0] = bytes[1];
bytes[1] = tmp;
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getShort();
}
}

View File

@ -0,0 +1,128 @@
package net.northking.cctp.scriptcase.tools;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
/**
* @author : yineng.huang
* @date : 2024/10/12 16:08
*/
public class InputStreamUtils {
private static final int DEFAULT_BUFFER_SIZE = 8192;
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
public static int readNBytes(InputStream inputStream,byte[] b, int off, int len) throws IOException {
checkFromIndexSize(off, len, b.length);
int n = 0;
while (n < len) {
int count = read(inputStream,b, off + n, len - n);
if (count < 0)
break;
n += count;
}
return n;
}
public static int read(InputStream inputStream,byte b[], int off, int len) throws IOException {
checkFromIndexSize(off, len, b.length);
if (len == 0) {
return 0;
}
int c = inputStream.read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = inputStream.read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
public static int checkFromIndexSize(int fromIndex, int size, int length) {
return Preconditions.checkFromIndexSize(fromIndex, size, length, null);
}
public static byte[] readNBytes(InputStream inputStream,int len) throws IOException {
if (len < 0) {
throw new IllegalArgumentException("len < 0");
}
List<byte[]> bufs = null;
byte[] result = null;
int total = 0;
int remaining = len;
int n;
do {
byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)];
int nread = 0;
// read to EOF which may read more or less than buffer size
while ((n = inputStream.read(buf, nread,
Math.min(buf.length - nread, remaining))) > 0) {
nread += n;
remaining -= n;
}
if (nread > 0) {
if (MAX_BUFFER_SIZE - total < nread) {
throw new OutOfMemoryError("Required array size too large");
}
if (nread < buf.length) {
buf = Arrays.copyOfRange(buf, 0, nread);
}
total += nread;
if (result == null) {
result = buf;
} else {
if (bufs == null) {
bufs = new ArrayList<>();
bufs.add(result);
}
bufs.add(buf);
}
}
// if the last call to read returned -1 or the number of bytes
// requested have been read then break
} while (n >= 0 && remaining > 0);
if (bufs == null) {
if (result == null) {
return new byte[0];
}
return result.length == total ?
result : Arrays.copyOf(result, total);
}
result = new byte[total];
int offset = 0;
remaining = total;
for (byte[] b : bufs) {
int count = Math.min(b.length, remaining);
System.arraycopy(b, 0, result, offset, count);
offset += count;
remaining -= count;
}
return result;
}
}

View File

@ -0,0 +1,483 @@
package net.northking.cctp.scriptcase.tools;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* @author : yineng.huang
* @date : 2024/10/12 16:44
*/
public class Preconditions {
/**
* Maps out-of-bounds values to a runtime exception.
*
* @param checkKind the kind of bounds check, whose name may correspond
* to the name of one of the range check methods, checkIndex,
* checkFromToIndex, checkFromIndexSize
* @param args the out-of-bounds arguments that failed the range check.
* If the checkKind corresponds a the name of a range check method
* then the bounds arguments are those that can be passed in order
* to the method.
* @param oobef the exception formatter that when applied with a checkKind
* and a list out-of-bounds arguments returns a runtime exception.
* If {@code null} then, it is as if an exception formatter was
* supplied that returns {@link IndexOutOfBoundsException} for any
* given arguments.
* @return the runtime exception
*/
private static RuntimeException outOfBounds(
BiFunction<String, List<Number>, ? extends RuntimeException> oobef,
String checkKind,
Number... args) {
List<Number> largs = Arrays.asList(args);
RuntimeException e = oobef == null
? null : oobef.apply(checkKind, largs);
return e == null
? new IndexOutOfBoundsException(outOfBoundsMessage(checkKind, largs)) : e;
}
private static RuntimeException outOfBoundsCheckIndex(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
int index, int length) {
return outOfBounds(oobe, "checkIndex", index, length);
}
private static RuntimeException outOfBoundsCheckFromToIndex(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
int fromIndex, int toIndex, int length) {
return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
}
private static RuntimeException outOfBoundsCheckFromIndexSize(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
int fromIndex, int size, int length) {
return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
}
private static RuntimeException outOfBoundsCheckIndex(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
long index, long length) {
return outOfBounds(oobe, "checkIndex", index, length);
}
private static RuntimeException outOfBoundsCheckFromToIndex(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
long fromIndex, long toIndex, long length) {
return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
}
private static RuntimeException outOfBoundsCheckFromIndexSize(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
long fromIndex, long size, long length) {
return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
}
/**
* Returns an out-of-bounds exception formatter from an given exception
* factory. The exception formatter is a function that formats an
* out-of-bounds message from its arguments and applies that message to the
* given exception factory to produce and relay an exception.
*
* <p>The exception formatter accepts two arguments: a {@code String}
* describing the out-of-bounds range check that failed, referred to as the
* <em>check kind</em>; and a {@code List<Number>} containing the
* out-of-bound integral values that failed the check. The list of
* out-of-bound values is not modified.
*
* <p>Three check kinds are supported {@code checkIndex},
* {@code checkFromToIndex} and {@code checkFromIndexSize} corresponding
* respectively to the specified application of an exception formatter as an
* argument to the out-of-bounds range check methods
* {@link #checkIndex(int, int, BiFunction) checkIndex},
* {@link #checkFromToIndex(int, int, int, BiFunction) checkFromToIndex}, and
* {@link #checkFromIndexSize(int, int, int, BiFunction) checkFromIndexSize}.
* Thus a supported check kind corresponds to a method name and the
* out-of-bound integral values correspond to method argument values, in
* order, preceding the exception formatter argument (similar in many
* respects to the form of arguments required for a reflective invocation of
* such a range check method).
*
* <p>Formatter arguments conforming to such supported check kinds will
* produce specific exception messages describing failed out-of-bounds
* checks. Otherwise, more generic exception messages will be produced in
* any of the following cases: the check kind is supported but fewer
* or more out-of-bounds values are supplied, the check kind is not
* supported, the check kind is {@code null}, or the list of out-of-bound
* values is {@code null}.
*
* @apiNote
* This method produces an out-of-bounds exception formatter that can be
* passed as an argument to any of the supported out-of-bounds range check
* methods declared by {@code Objects}. For example, a formatter producing
* an {@code ArrayIndexOutOfBoundsException} may be produced and stored on a
* {@code static final} field as follows:
* <pre>{@code
* static final
* BiFunction<String, List<Number>, ArrayIndexOutOfBoundsException> AIOOBEF =
* outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
* }</pre>
* The formatter instance {@code AIOOBEF} may be passed as an argument to an
* out-of-bounds range check method, such as checking if an {@code index}
* is within the bounds of a {@code limit}:
* <pre>{@code
* checkIndex(index, limit, AIOOBEF);
* }</pre>
* If the bounds check fails then the range check method will throw an
* {@code ArrayIndexOutOfBoundsException} with an appropriate exception
* message that is a produced from {@code AIOOBEF} as follows:
* <pre>{@code
* AIOOBEF.apply("checkIndex", List.of(index, limit));
* }</pre>
*
* @param f the exception factory, that produces an exception from a message
* where the message is produced and formatted by the returned
* exception formatter. If this factory is stateless and side-effect
* free then so is the returned formatter.
* Exceptions thrown by the factory are relayed to the caller
* of the returned formatter.
* @param <X> the type of runtime exception to be returned by the given
* exception factory and relayed by the exception formatter
* @return the out-of-bounds exception formatter
*/
public static <X extends RuntimeException>
BiFunction<String, List<Number>, X> outOfBoundsExceptionFormatter(Function<String, X> f) {
// Use anonymous class to avoid bootstrap issues if this method is
// used early in startup
return new BiFunction<String, List<Number>, X>() {
@Override
public X apply(String checkKind, List<Number> args) {
return f.apply(outOfBoundsMessage(checkKind, args));
}
};
}
private static String outOfBoundsMessage(String checkKind, List<? extends Number> args) {
if (checkKind == null && args == null) {
return String.format("Range check failed");
} else if (checkKind == null) {
return String.format("Range check failed: %s", args);
} else if (args == null) {
return String.format("Range check failed: %s", checkKind);
}
int argSize = 0;
switch (checkKind) {
case "checkIndex":
argSize = 2;
break;
case "checkFromToIndex":
case "checkFromIndexSize":
argSize = 3;
break;
default:
}
// Switch to default if fewer or more arguments than required are supplied
switch ((args.size() != argSize) ? "" : checkKind) {
case "checkIndex":
return String.format("Index %s out of bounds for length %s",
args.get(0), args.get(1));
case "checkFromToIndex":
return String.format("Range [%s, %s) out of bounds for length %s",
args.get(0), args.get(1), args.get(2));
case "checkFromIndexSize":
return String.format("Range [%s, %<s + %s) out of bounds for length %s",
args.get(0), args.get(1), args.get(2));
default:
return String.format("Range check failed: %s %s", checkKind, args);
}
}
/**
* Checks if the {@code index} is within the bounds of the range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
* <p>The {@code index} is defined to be out of bounds if any of the
* following inequalities is true:
* <ul>
* <li>{@code index < 0}</li>
* <li>{@code index >= length}</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the {@code index} is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkIndex};
* and an unmodifiable list of integers whose values are, in order, the
* out-of-bounds arguments {@code index} and {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param index the index
* @param length the upper-bound (exclusive) of the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code index} if it is within bounds of the range
* @throws X if the {@code index} is out of bounds and the exception
* formatter is non-{@code null}
* @throws IndexOutOfBoundsException if the {@code index} is out of bounds
* and the exception formatter is {@code null}
* @since 9
*
* @implNote
* This method is made intrinsic in optimizing compilers to guide them to
* perform unsigned comparisons of the index and length when it is known the
* length is a non-negative value (such as that of an array length or from
* the upper bound of a loop)
*/
public static <X extends RuntimeException>
int checkIndex(int index, int length,
BiFunction<String, List<Number>, X> oobef) {
if (index < 0 || index >= length)
throw outOfBoundsCheckIndex(oobef, index, length);
return index;
}
/**
* Checks if the sub-range from {@code fromIndex} (inclusive) to
* {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
* (inclusive) to {@code length} (exclusive).
*
* <p>The sub-range is defined to be out of bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
* <li>{@code fromIndex > toIndex}</li>
* <li>{@code toIndex > length}</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the sub-range is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkFromToIndex};
* and an unmodifiable list of integers whose values are, in order, the
* out-of-bounds arguments {@code fromIndex}, {@code toIndex}, and {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param fromIndex the lower-bound (inclusive) of the sub-range
* @param toIndex the upper-bound (exclusive) of the sub-range
* @param length the upper-bound (exclusive) the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
* @throws X if the sub-range is out of bounds and the exception factory
* function is non-{@code null}
* @throws IndexOutOfBoundsException if the sub-range is out of bounds and
* the exception factory function is {@code null}
* @since 9
*/
public static <X extends RuntimeException>
int checkFromToIndex(int fromIndex, int toIndex, int length,
BiFunction<String, List<Number>, X> oobef) {
if (fromIndex < 0 || fromIndex > toIndex || toIndex > length)
throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
return fromIndex;
}
/**
* Checks if the sub-range from {@code fromIndex} (inclusive) to
* {@code fromIndex + size} (exclusive) is within the bounds of range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
* <p>The sub-range is defined to be out of bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
* <li>{@code size < 0}</li>
* <li>{@code fromIndex + size > length}, taking into account integer overflow</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the sub-range is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkFromIndexSize};
* and an unmodifiable list of integers whose values are, in order, the
* out-of-bounds arguments {@code fromIndex}, {@code size}, and
* {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param fromIndex the lower-bound (inclusive) of the sub-interval
* @param size the size of the sub-range
* @param length the upper-bound (exclusive) of the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
* @throws X if the sub-range is out of bounds and the exception factory
* function is non-{@code null}
* @throws IndexOutOfBoundsException if the sub-range is out of bounds and
* the exception factory function is {@code null}
* @since 9
*/
public static <X extends RuntimeException>
int checkFromIndexSize(int fromIndex, int size, int length,
BiFunction<String, List<Number>, X> oobef) {
if ((length | fromIndex | size) < 0 || size > length - fromIndex)
throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
return fromIndex;
}
/**
* Checks if the {@code index} is within the bounds of the range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
* <p>The {@code index} is defined to be out of bounds if any of the
* following inequalities is true:
* <ul>
* <li>{@code index < 0}</li>
* <li>{@code index >= length}</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the {@code index} is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkIndex};
* and an unmodifiable list of longs whose values are, in order, the
* out-of-bounds arguments {@code index} and {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param index the index
* @param length the upper-bound (exclusive) of the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code index} if it is within bounds of the range
* @throws X if the {@code index} is out of bounds and the exception
* formatter is non-{@code null}
* @throws IndexOutOfBoundsException if the {@code index} is out of bounds
* and the exception formatter is {@code null}
* @since 16
*
* @implNote
* This method is made intrinsic in optimizing compilers to guide them to
* perform unsigned comparisons of the index and length when it is known the
* length is a non-negative value (such as that of an array length or from
* the upper bound of a loop)
*/
public static <X extends RuntimeException>
long checkIndex(long index, long length,
BiFunction<String, List<Number>, X> oobef) {
if (index < 0 || index >= length)
throw outOfBoundsCheckIndex(oobef, index, length);
return index;
}
/**
* Checks if the sub-range from {@code fromIndex} (inclusive) to
* {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
* (inclusive) to {@code length} (exclusive).
*
* <p>The sub-range is defined to be out of bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
* <li>{@code fromIndex > toIndex}</li>
* <li>{@code toIndex > length}</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the sub-range is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkFromToIndex};
* and an unmodifiable list of longs whose values are, in order, the
* out-of-bounds arguments {@code fromIndex}, {@code toIndex}, and {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param fromIndex the lower-bound (inclusive) of the sub-range
* @param toIndex the upper-bound (exclusive) of the sub-range
* @param length the upper-bound (exclusive) the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
* @throws X if the sub-range is out of bounds and the exception factory
* function is non-{@code null}
* @throws IndexOutOfBoundsException if the sub-range is out of bounds and
* the exception factory function is {@code null}
* @since 16
*/
public static <X extends RuntimeException>
long checkFromToIndex(long fromIndex, long toIndex, long length,
BiFunction<String, List<Number>, X> oobef) {
if (fromIndex < 0 || fromIndex > toIndex || toIndex > length)
throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
return fromIndex;
}
/**
* Checks if the sub-range from {@code fromIndex} (inclusive) to
* {@code fromIndex + size} (exclusive) is within the bounds of range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
* <p>The sub-range is defined to be out of bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
* <li>{@code size < 0}</li>
* <li>{@code fromIndex + size > length}, taking into account integer overflow</li>
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
* <p>If the sub-range is out of bounds, then a runtime exception is
* thrown that is the result of applying the following arguments to the
* exception formatter: the name of this method, {@code checkFromIndexSize};
* and an unmodifiable list of longs whose values are, in order, the
* out-of-bounds arguments {@code fromIndex}, {@code size}, and
* {@code length}.
*
* @param <X> the type of runtime exception to throw if the arguments are
* out of bounds
* @param fromIndex the lower-bound (inclusive) of the sub-interval
* @param size the size of the sub-range
* @param length the upper-bound (exclusive) of the range
* @param oobef the exception formatter that when applied with this
* method name and out-of-bounds arguments returns a runtime
* exception. If {@code null} or returns {@code null} then, it is as
* if an exception formatter produced from an invocation of
* {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
* instead (though it may be more efficient).
* Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
* @throws X if the sub-range is out of bounds and the exception factory
* function is non-{@code null}
* @throws IndexOutOfBoundsException if the sub-range is out of bounds and
* the exception factory function is {@code null}
* @since 16
*/
public static <X extends RuntimeException>
long checkFromIndexSize(long fromIndex, long size, long length,
BiFunction<String, List<Number>, X> oobef) {
if ((length | fromIndex | size) < 0 || size > length - fromIndex)
throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
return fromIndex;
}
}

View File

@ -0,0 +1,161 @@
package net.northking.cctp.scriptcase.tools.hapUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import net.northking.cctp.scriptcase.tools.hapUtil.module.Ability;
import net.northking.cctp.scriptcase.tools.hapUtil.module.HapModule;
import net.northking.cctp.scriptcase.tools.hapUtil.pack.HapPackInfo;
import net.northking.cctp.scriptcase.tools.hapUtil.resources.RecordItem;
import net.northking.cctp.scriptcase.tools.hapUtil.resources.ResType;
import net.northking.cctp.scriptcase.tools.hapUtil.resources.ResourcesIndex;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Slf4j
public class Hap implements Closeable {
/**
*
*/
private static final String MODULE_INFO_FILE_NAME = "module.json";
/**
*
*/
private static final String PACK_INFO_FILE_NAME = "pack.info";
private static final String RESOURCE_INDEX_FILE_NAME = "resources.index";
private static final Gson gson = new Gson();
private HapModule hapModule;
private HapPackInfo hapPackInfo;
private ResourcesIndex resourcesIndex;
private final ZipFile zipFile;
public Hap(File file) throws IOException {
zipFile = new ZipFile(file);
ZipEntry entry = zipFile.getEntry(MODULE_INFO_FILE_NAME);
InputStream inputStream = zipFile.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
hapModule = gson.fromJson(builder.toString(), HapModule.class);
inputStream = zipFile.getInputStream(zipFile.getEntry(PACK_INFO_FILE_NAME));
reader = new BufferedReader(new InputStreamReader(inputStream));
builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
hapPackInfo = gson.fromJson(builder.toString(), HapPackInfo.class);
ZipEntry resIndexEntry = zipFile.getEntry(RESOURCE_INDEX_FILE_NAME);
resourcesIndex = new ResourcesIndex(zipFile.getInputStream(resIndexEntry), resIndexEntry.getSize());
}
public HapModule getHapModule() {
return hapModule;
}
public HapPackInfo getHapPackInfo() {
return hapPackInfo;
}
public ResourcesIndex getResourcesIndex() {
return resourcesIndex;
}
public String getVersionName() {
return hapModule.getApp().getVersionName();
}
public long getVersionCode() {
return hapModule.getApp().getVersionCode();
}
public String getAppName() {
int labelId = hapModule.getApp().getLabelId();
Ability homeAbility = hapModule.getModule().getDefaultHomeAbility();
if (homeAbility != null) {
labelId = homeAbility.getLabelId();
}
String name = null;
RecordItem recordItem = resourcesIndex.getRecordItemById(labelId);
while (name == null) {
if (recordItem == null) {
log.warn("获取Hap应用名称时找不到资源索引");
return null;
}
if (recordItem.getResType() != ResType.STRING) {
log.warn("获取Hap应用名称时索引资源类型不为字符串实际为{}", recordItem.getResType());
return null;
}
if (recordItem.getValue().startsWith("$string:")) {
try {
labelId = Integer.parseInt(recordItem.getValue().split(":")[1]);
} catch (NumberFormatException e) {
log.error("无法从字符串[{}]获取次级资源id", recordItem.getValue());
return recordItem.getValue();
}
recordItem = resourcesIndex.getRecordItemById(labelId);
} else {
name = recordItem.getValue();
}
}
return name;
}
/**
*
* <br>
* 使
*
* @return
*/
public InputStream getAppIcon() {
int iconId = hapModule.getApp().getIconId();
Ability homeAbility = hapModule.getModule().getDefaultHomeAbility();
if (homeAbility != null) {
iconId = homeAbility.getStartWindowIconId();//不使用IconId是因为指向的是分层图标json
}
RecordItem recordItem = resourcesIndex.getRecordItemById(iconId);
if (recordItem == null) {
log.warn("获取Hap应用图标时找不到资源索引");
return null;
}
if (recordItem.getResType() != ResType.MEDIA) {
log.warn("获取Hap应用图标时索引资源类型不为媒体实际为{}", recordItem.getResType());
return null;
}
ZipEntry entry = zipFile.getEntry(recordItem.getValue().replace(hapModule.getModule().getName() + "/", ""));
if (entry == null) {
log.error("获取Hap应用图标时包内未找到图标文件{}", recordItem.getValue());
return null;
}
InputStream inputStream = null;
try {
inputStream = zipFile.getInputStream(entry);
} catch (IOException e) {
log.error("获取Hap应用图标时获取包内图片文件读取流时发生IO错误", e);
}
return inputStream;
}
@Override
public void close() {
try {
zipFile.close();
} catch (IOException e) {
log.error("关闭Hap资源时发生IO错误", e);
}
}
}

View File

@ -0,0 +1,152 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Ability {
@SerializedName("description")
private String mDescription;
@SerializedName("descriptionId")
private int mDescriptionId;
@SerializedName("exported")
private Boolean mExported;
@SerializedName("icon")
private String mIcon;
@SerializedName("iconId")
private int mIconId;
@SerializedName("label")
private String mLabel;
@SerializedName("labelId")
private int mLabelId;
@SerializedName("name")
private String mName;
@SerializedName("skills")
private List<Skill> mSkills;
@SerializedName("srcEntry")
private String mSrcEntry;
@SerializedName("startWindowBackground")
private String mStartWindowBackground;
@SerializedName("startWindowBackgroundId")
private int mStartWindowBackgroundId;
@SerializedName("startWindowIcon")
private String mStartWindowIcon;
@SerializedName("startWindowIconId")
private int mStartWindowIconId;
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
mDescription = description;
}
public int getDescriptionId() {
return mDescriptionId;
}
public void setDescriptionId(int descriptionId) {
mDescriptionId = descriptionId;
}
public Boolean getExported() {
return mExported;
}
public void setExported(Boolean exported) {
mExported = exported;
}
public String getIcon() {
return mIcon;
}
public void setIcon(String icon) {
mIcon = icon;
}
public int getIconId() {
return mIconId;
}
public void setIconId(int iconId) {
mIconId = iconId;
}
public String getLabel() {
return mLabel;
}
public void setLabel(String label) {
mLabel = label;
}
public int getLabelId() {
return mLabelId;
}
public void setLabelId(int labelId) {
mLabelId = labelId;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public List<Skill> getSkills() {
return mSkills;
}
public void setSkills(List<Skill> skills) {
mSkills = skills;
}
public String getSrcEntry() {
return mSrcEntry;
}
public void setSrcEntry(String srcEntry) {
mSrcEntry = srcEntry;
}
public String getStartWindowBackground() {
return mStartWindowBackground;
}
public void setStartWindowBackground(String startWindowBackground) {
mStartWindowBackground = startWindowBackground;
}
public int getStartWindowBackgroundId() {
return mStartWindowBackgroundId;
}
public void setStartWindowBackgroundId(int startWindowBackgroundId) {
mStartWindowBackgroundId = startWindowBackgroundId;
}
public String getStartWindowIcon() {
return mStartWindowIcon;
}
public void setStartWindowIcon(String startWindowIcon) {
mStartWindowIcon = startWindowIcon;
}
public int getStartWindowIconId() {
return mStartWindowIconId;
}
public void setStartWindowIconId(int startWindowIconId) {
mStartWindowIconId = startWindowIconId;
}
}

View File

@ -0,0 +1,172 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class App {
@SerializedName("apiReleaseType")
private String mApiReleaseType;
@SerializedName("appEnvironments")
private List<Object> mAppEnvironments;
@SerializedName("bundleName")
private String mBundleName;
@SerializedName("bundleType")
private String mBundleType;
@SerializedName("compileSdkType")
private String mCompileSdkType;
@SerializedName("compileSdkVersion")
private String mCompileSdkVersion;
@SerializedName("debug")
private Boolean mDebug;
@SerializedName("icon")
private String mIcon;
@SerializedName("iconId")
private int mIconId;
@SerializedName("label")
private String mLabel;
@SerializedName("labelId")
private int mLabelId;
@SerializedName("minAPIVersion")
private Long mMinAPIVersion;
@SerializedName("targetAPIVersion")
private Long mTargetAPIVersion;
@SerializedName("vendor")
private String mVendor;
@SerializedName("versionCode")
private Long mVersionCode;
@SerializedName("versionName")
private String mVersionName;
public String getApiReleaseType() {
return mApiReleaseType;
}
public void setApiReleaseType(String apiReleaseType) {
mApiReleaseType = apiReleaseType;
}
public List<Object> getAppEnvironments() {
return mAppEnvironments;
}
public void setAppEnvironments(List<Object> appEnvironments) {
mAppEnvironments = appEnvironments;
}
public String getBundleName() {
return mBundleName;
}
public void setBundleName(String bundleName) {
mBundleName = bundleName;
}
public String getBundleType() {
return mBundleType;
}
public void setBundleType(String bundleType) {
mBundleType = bundleType;
}
public String getCompileSdkType() {
return mCompileSdkType;
}
public void setCompileSdkType(String compileSdkType) {
mCompileSdkType = compileSdkType;
}
public String getCompileSdkVersion() {
return mCompileSdkVersion;
}
public void setCompileSdkVersion(String compileSdkVersion) {
mCompileSdkVersion = compileSdkVersion;
}
public Boolean getDebug() {
return mDebug;
}
public void setDebug(Boolean debug) {
mDebug = debug;
}
public String getIcon() {
return mIcon;
}
public void setIcon(String icon) {
mIcon = icon;
}
public int getIconId() {
return mIconId;
}
public void setIconId(int iconId) {
mIconId = iconId;
}
public String getLabel() {
return mLabel;
}
public void setLabel(String label) {
mLabel = label;
}
public int getLabelId() {
return mLabelId;
}
public void setLabelId(int labelId) {
mLabelId = labelId;
}
public Long getMinAPIVersion() {
return mMinAPIVersion;
}
public void setMinAPIVersion(Long minAPIVersion) {
mMinAPIVersion = minAPIVersion;
}
public Long getTargetAPIVersion() {
return mTargetAPIVersion;
}
public void setTargetAPIVersion(Long targetAPIVersion) {
mTargetAPIVersion = targetAPIVersion;
}
public String getVendor() {
return mVendor;
}
public void setVendor(String vendor) {
mVendor = vendor;
}
public Long getVersionCode() {
return mVersionCode;
}
public void setVersionCode(Long versionCode) {
mVersionCode = versionCode;
}
public String getVersionName() {
return mVersionName;
}
public void setVersionName(String versionName) {
mVersionName = versionName;
}
}

View File

@ -0,0 +1,62 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class ExtensionAbility {
@SerializedName("exported")
private Boolean mExported;
@SerializedName("metadata")
private List<Metadatum> mMetadata;
@SerializedName("name")
private String mName;
@SerializedName("srcEntry")
private String mSrcEntry;
@SerializedName("type")
private String mType;
public Boolean getExported() {
return mExported;
}
public void setExported(Boolean exported) {
mExported = exported;
}
public List<Metadatum> getMetadata() {
return mMetadata;
}
public void setMetadata(List<Metadatum> metadata) {
mMetadata = metadata;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getSrcEntry() {
return mSrcEntry;
}
public void setSrcEntry(String srcEntry) {
mSrcEntry = srcEntry;
}
public String getType() {
return mType;
}
public void setType(String type) {
mType = type;
}
}

View File

@ -0,0 +1,30 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class HapModule {
@SerializedName("app")
private App mApp;
@SerializedName("module")
private Module mModule;
public App getApp() {
return mApp;
}
public void setApp(App app) {
mApp = app;
}
public Module getModule() {
return mModule;
}
public void setModule(Module module) {
mModule = module;
}
}

View File

@ -0,0 +1,40 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class Metadatum {
@SerializedName("name")
private String mName;
@SerializedName("resource")
private String mResource;
@SerializedName("resourceId")
private Long mResourceId;
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getResource() {
return mResource;
}
public void setResource(String resource) {
mResource = resource;
}
public Long getResourceId() {
return mResourceId;
}
public void setResourceId(Long resourceId) {
mResourceId = resourceId;
}
}

View File

@ -0,0 +1,178 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Module {
@SerializedName("abilities")
private List<Ability> mAbilities;
@SerializedName("compileMode")
private String mCompileMode;
@SerializedName("deliveryWithInstall")
private Boolean mDeliveryWithInstall;
@SerializedName("dependencies")
private List<Object> mDependencies;
@SerializedName("description")
private String mDescription;
@SerializedName("descriptionId")
private Long mDescriptionId;
@SerializedName("deviceTypes")
private List<String> mDeviceTypes;
@SerializedName("extensionAbilities")
private List<ExtensionAbility> mExtensionAbilities;
@SerializedName("installationFree")
private Boolean mInstallationFree;
@SerializedName("mainElement")
private String mMainElement;
@SerializedName("name")
private String mName;
@SerializedName("packageName")
private String mPackageName;
@SerializedName("pages")
private String mPages;
@SerializedName("type")
private String mType;
@SerializedName("virtualMachine")
private String mVirtualMachine;
public List<Ability> getAbilities() {
return mAbilities;
}
public void setAbilities(List<Ability> abilities) {
mAbilities = abilities;
}
public String getCompileMode() {
return mCompileMode;
}
public void setCompileMode(String compileMode) {
mCompileMode = compileMode;
}
public Boolean getDeliveryWithInstall() {
return mDeliveryWithInstall;
}
public void setDeliveryWithInstall(Boolean deliveryWithInstall) {
mDeliveryWithInstall = deliveryWithInstall;
}
public List<Object> getDependencies() {
return mDependencies;
}
public void setDependencies(List<Object> dependencies) {
mDependencies = dependencies;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
mDescription = description;
}
public Long getDescriptionId() {
return mDescriptionId;
}
public void setDescriptionId(Long descriptionId) {
mDescriptionId = descriptionId;
}
public List<String> getDeviceTypes() {
return mDeviceTypes;
}
public void setDeviceTypes(List<String> deviceTypes) {
mDeviceTypes = deviceTypes;
}
public List<ExtensionAbility> getExtensionAbilities() {
return mExtensionAbilities;
}
public void setExtensionAbilities(List<ExtensionAbility> extensionAbilities) {
mExtensionAbilities = extensionAbilities;
}
public Boolean getInstallationFree() {
return mInstallationFree;
}
public void setInstallationFree(Boolean installationFree) {
mInstallationFree = installationFree;
}
public String getMainElement() {
return mMainElement;
}
public void setMainElement(String mainElement) {
mMainElement = mainElement;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
mPackageName = packageName;
}
public String getPages() {
return mPages;
}
public void setPages(String pages) {
mPages = pages;
}
public String getType() {
return mType;
}
public void setType(String type) {
mType = type;
}
public String getVirtualMachine() {
return mVirtualMachine;
}
public void setVirtualMachine(String virtualMachine) {
mVirtualMachine = virtualMachine;
}
public Ability getDefaultHomeAbility() {
if (mAbilities == null) return null;
for (Ability ability : mAbilities) {
List<Skill> skill = ability.getSkills();
if (skill != null) {
for (Skill s : skill) {
if (s.isDefaultHome()) {
return ability;
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,38 @@
package net.northking.cctp.scriptcase.tools.hapUtil.module;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Skill {
private static final String ACTION_SYSTEM_HOME = "action.system.home";
private static final String ENTRY_SYSTEM_HOME = "entity.system.home";
@SerializedName("actions")
private List<String> mActions;
@SerializedName("entities")
private List<String> mEntities;
public List<String> getActions() {
return mActions;
}
public void setActions(List<String> actions) {
mActions = actions;
}
public List<String> getEntities() {
return mEntities;
}
public void setEntities(List<String> entities) {
mEntities = entities;
}
public boolean isDefaultHome() {
return mActions != null && mActions.contains(ACTION_SYSTEM_HOME) && mEntities != null && mEntities.contains(ENTRY_SYSTEM_HOME);
}
}

View File

@ -0,0 +1,30 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class Ability {
@SerializedName("label")
private String mLabel;
@SerializedName("name")
private String mName;
public String getLabel() {
return mLabel;
}
public void setLabel(String label) {
mLabel = label;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}

View File

@ -0,0 +1,40 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class ApiVersion {
@SerializedName("compatible")
private Long mCompatible;
@SerializedName("releaseType")
private String mReleaseType;
@SerializedName("target")
private Long mTarget;
public Long getCompatible() {
return mCompatible;
}
public void setCompatible(Long compatible) {
mCompatible = compatible;
}
public String getReleaseType() {
return mReleaseType;
}
public void setReleaseType(String releaseType) {
mReleaseType = releaseType;
}
public Long getTarget() {
return mTarget;
}
public void setTarget(Long target) {
mTarget = target;
}
}

View File

@ -0,0 +1,40 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class App {
@SerializedName("bundleName")
private String mBundleName;
@SerializedName("bundleType")
private String mBundleType;
@SerializedName("version")
private Version mVersion;
public String getBundleName() {
return mBundleName;
}
public void setBundleName(String bundleName) {
mBundleName = bundleName;
}
public String getBundleType() {
return mBundleType;
}
public void setBundleType(String bundleType) {
mBundleType = bundleType;
}
public Version getVersion() {
return mVersion;
}
public void setVersion(Version version) {
mVersion = version;
}
}

View File

@ -0,0 +1,50 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class Distro {
@SerializedName("deliveryWithInstall")
private Boolean mDeliveryWithInstall;
@SerializedName("installationFree")
private Boolean mInstallationFree;
@SerializedName("moduleName")
private String mModuleName;
@SerializedName("moduleType")
private String mModuleType;
public Boolean getDeliveryWithInstall() {
return mDeliveryWithInstall;
}
public void setDeliveryWithInstall(Boolean deliveryWithInstall) {
mDeliveryWithInstall = deliveryWithInstall;
}
public Boolean getInstallationFree() {
return mInstallationFree;
}
public void setInstallationFree(Boolean installationFree) {
mInstallationFree = installationFree;
}
public String getModuleName() {
return mModuleName;
}
public void setModuleName(String moduleName) {
mModuleName = moduleName;
}
public String getModuleType() {
return mModuleType;
}
public void setModuleType(String moduleType) {
mModuleType = moduleType;
}
}

View File

@ -0,0 +1,32 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class ExtensionAbility {
@SerializedName("forms")
private List<Object> mForms;
@SerializedName("name")
private String mName;
public List<Object> getForms() {
return mForms;
}
public void setForms(List<Object> forms) {
mForms = forms;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}

View File

@ -0,0 +1,32 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class HapPackInfo {
@SerializedName("packages")
private List<Package> mPackages;
@SerializedName("summary")
private Summary mSummary;
public List<Package> getPackages() {
return mPackages;
}
public void setPackages(List<Package> packages) {
mPackages = packages;
}
public Summary getSummary() {
return mSummary;
}
public void setSummary(Summary summary) {
mSummary = summary;
}
}

View File

@ -0,0 +1,72 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Module {
@SerializedName("abilities")
private List<Ability> mAbilities;
@SerializedName("apiVersion")
private ApiVersion mApiVersion;
@SerializedName("deviceType")
private List<String> mDeviceType;
@SerializedName("distro")
private Distro mDistro;
@SerializedName("extensionAbilities")
private List<ExtensionAbility> mExtensionAbilities;
@SerializedName("mainAbility")
private String mMainAbility;
public List<Ability> getAbilities() {
return mAbilities;
}
public void setAbilities(List<Ability> abilities) {
mAbilities = abilities;
}
public ApiVersion getApiVersion() {
return mApiVersion;
}
public void setApiVersion(ApiVersion apiVersion) {
mApiVersion = apiVersion;
}
public List<String> getDeviceType() {
return mDeviceType;
}
public void setDeviceType(List<String> deviceType) {
mDeviceType = deviceType;
}
public Distro getDistro() {
return mDistro;
}
public void setDistro(Distro distro) {
mDistro = distro;
}
public List<ExtensionAbility> getExtensionAbilities() {
return mExtensionAbilities;
}
public void setExtensionAbilities(List<ExtensionAbility> extensionAbilities) {
mExtensionAbilities = extensionAbilities;
}
public String getMainAbility() {
return mMainAbility;
}
public void setMainAbility(String mainAbility) {
mMainAbility = mainAbility;
}
}

View File

@ -0,0 +1,52 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Package {
@SerializedName("deliveryWithInstall")
private Boolean mDeliveryWithInstall;
@SerializedName("deviceType")
private List<String> mDeviceType;
@SerializedName("moduleType")
private String mModuleType;
@SerializedName("name")
private String mName;
public Boolean getDeliveryWithInstall() {
return mDeliveryWithInstall;
}
public void setDeliveryWithInstall(Boolean deliveryWithInstall) {
mDeliveryWithInstall = deliveryWithInstall;
}
public List<String> getDeviceType() {
return mDeviceType;
}
public void setDeviceType(List<String> deviceType) {
mDeviceType = deviceType;
}
public String getModuleType() {
return mModuleType;
}
public void setModuleType(String moduleType) {
mModuleType = moduleType;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}

View File

@ -0,0 +1,32 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings("unused")
public class Summary {
@SerializedName("app")
private App mApp;
@SerializedName("modules")
private List<Module> mModules;
public App getApp() {
return mApp;
}
public void setApp(App app) {
mApp = app;
}
public List<Module> getModules() {
return mModules;
}
public void setModules(List<Module> modules) {
mModules = modules;
}
}

View File

@ -0,0 +1,30 @@
package net.northking.cctp.scriptcase.tools.hapUtil.pack;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class Version {
@SerializedName("code")
private Long mCode;
@SerializedName("name")
private String mName;
public Long getCode() {
return mCode;
}
public void setCode(Long code) {
mCode = code;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}

View File

@ -0,0 +1,12 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public class IdSet {
int idCount;
IdData[] idData;
RecordItem[] recordItems;
public static class IdData {
public int id;
public int offset;
}
}

View File

@ -0,0 +1,7 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public class IndexHeader {
String version;
int fileSize;
int limitKeyConfigSize;
}

View File

@ -0,0 +1,26 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public class KeyParam {
private KeyType type;
private int value;
public KeyType getType() {
return type;
}
public void setType(KeyType type) {
this.type = type;
}
public void setType(int value) {
this.type = KeyType.valueOf(value);
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,32 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public enum KeyType {
LANGUAGE(0),
REGION(1),
RESOLUTION(2),
ORIENTATION(3),
DEVICETYPE(4),
SCRIPT(5),
NIGHTMODE(6),
MCC(7),
MNC(8),
RESERVER(9),
INPUTDEVICE(10),
KEY_TYPE_MAX(11),
OTHER(-1),
;
private final int value;
KeyType(int v) {
this.value = v;
}
public static KeyType valueOf(int v) {
for (KeyType key : KeyType.values()) {
if (key.value == v) {
return key;
}
}
return OTHER;
}
}

View File

@ -0,0 +1,15 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public class LimitKeyConfig {
/**
* IdSet file address offset
*/
int offset;
/**
* KeyParam count
*/
int keyCont;
KeyParam[] data;
}

View File

@ -0,0 +1,53 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public class RecordItem {
private int size;
private ResType resType;
private int id;
private String name;
private String value;
public ResType getResType() {
return resType;
}
public void setResType(ResType resType) {
this.resType = resType;
}
public void setResType(int resType) {
this.resType = ResType.valueOf(resType);
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,37 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
public enum ResType {
ELEMENT(0),
RAW(6),
INTEGER(8),
STRING(9),
STRARRAY(10),
INTARRAY(11),
BOOLEAN(12),
COLOR(14),
ID(15),
THEME(16),
PLURAL(17),
FLOAT(18),
MEDIA(19),
PROF(20),
PATTERN(22),
SYMBOL(23),
RES(24),
INVALID_RES_TYPE(-1),
;
private final int value;
private ResType(final int value) {
this.value = value;
}
public static ResType valueOf(final int value) {
for (ResType type : ResType.values()) {
if (type.value == value) {
return type;
}
}
return ResType.INVALID_RES_TYPE;
}
}

View File

@ -0,0 +1,137 @@
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
import lombok.extern.slf4j.Slf4j;
import net.northking.cctp.scriptcase.tools.ByteUtils;
import net.northking.cctp.scriptcase.tools.InputStreamUtils;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
public class ResourcesIndex {
private static final String HEADER_PREFIX = "Restool ";
private final IndexHeader indexHeader = new IndexHeader();
private LimitKeyConfig[] limitKeyConfigs = new LimitKeyConfig[0];
private IdSet[] idSets = new IdSet[0];
public ResourcesIndex(InputStream stream, long dataSize) throws IOException {
DataInputStream dataInput = new DataInputStream(stream);
byte[] headerBytes = new byte[0x80];
try {
dataInput.readFully(headerBytes);
} catch (IOException e) {
log.error("解析Hap文件resource.index时发生IO错误", e);
return;
}
int stopIndex = -1;
for (int i = 0; i < headerBytes.length; i++) {
if (headerBytes[i] == 0) {
stopIndex = i;
break;
}
}
if (stopIndex == -1) {
throw new IOException("无法定位版本结束位置");
}
String header = new String(headerBytes, 0, stopIndex);
String[] headers = header.split(" ");
if (headers.length != 2) {
throw new IOException("无法处理版本字符串:" + header);
}
indexHeader.version = headers[1];
byte[] sizeByteArray = new byte[4];
dataInput.readFully(sizeByteArray);
int fileSize = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
if (fileSize != dataSize) {
throw new IOException("文件记录的大小与压缩包给定大小不一致");
}
indexHeader.fileSize = fileSize;
dataInput.readFully(sizeByteArray);
indexHeader.limitKeyConfigSize = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
limitKeyConfigs = new LimitKeyConfig[indexHeader.limitKeyConfigSize];
for (int i = 0; i < indexHeader.limitKeyConfigSize; i++) {
LimitKeyConfig limitKeyConfig = new LimitKeyConfig();
for (int j = 0; j < 4; j++) {
dataInput.read();
}
dataInput.readFully(sizeByteArray);
limitKeyConfig.offset = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
dataInput.readFully(sizeByteArray);
limitKeyConfig.keyCont = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
limitKeyConfig.data = new KeyParam[limitKeyConfig.keyCont];
for (int j = 0; j < limitKeyConfig.keyCont; j++) {
KeyParam keyParam = new KeyParam();
dataInput.readFully(sizeByteArray);
keyParam.setType(ByteUtils.littleEndianByteArrayToInt(sizeByteArray));
dataInput.readFully(sizeByteArray);
keyParam.setValue(ByteUtils.littleEndianByteArrayToInt(sizeByteArray));
limitKeyConfig.data[j] = keyParam;
}
limitKeyConfigs[i] = limitKeyConfig;
}
idSets = new IdSet[indexHeader.limitKeyConfigSize];
for (int i = 0; i < indexHeader.limitKeyConfigSize; i++) {
for (int j = 0; j < 4; j++) {
dataInput.read();
}
IdSet idSet = new IdSet();
dataInput.readFully(sizeByteArray);
idSet.idCount = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
idSet.idData = new IdSet.IdData[idSet.idCount];
for (int j = 0; j < idSet.idCount; j++) {
IdSet.IdData idData = new IdSet.IdData();
dataInput.readFully(sizeByteArray);
idData.id = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
dataInput.readFully(sizeByteArray);
idData.offset = ByteUtils.littleEndianByteArrayToInt(sizeByteArray);
idSet.idData[j] = idData;
}
idSets[i] = idSet;
}
for (int i = 0; i < indexHeader.limitKeyConfigSize; i++) {
IdSet idSet = idSets[i];
idSet.recordItems = new RecordItem[idSet.idCount];
for (int j = 0; j < idSet.idCount; j++) {
RecordItem recordItem = new RecordItem();
dataInput.readFully(sizeByteArray);
recordItem.setSize(ByteUtils.littleEndianByteArrayToInt(sizeByteArray));
dataInput.readFully(sizeByteArray);
recordItem.setResType(ByteUtils.littleEndianByteArrayToInt(sizeByteArray));
dataInput.readFully(sizeByteArray);
recordItem.setId(ByteUtils.littleEndianByteArrayToInt(sizeByteArray));
dataInput.readFully(sizeByteArray, 0, 2);
int length = ByteUtils.littleEndianByteArrayToShort(sizeByteArray);
recordItem.setValue(new String(InputStreamUtils.readNBytes(dataInput,length), 0, length - 1));
dataInput.readFully(sizeByteArray, 0, 2);
length = ByteUtils.littleEndianByteArrayToShort(sizeByteArray);
recordItem.setName(new String(InputStreamUtils.readNBytes(dataInput,length), 0, length - 1));
idSet.recordItems[j] = recordItem;
}
}
}
public IndexHeader getIndexHeader() {
return indexHeader;
}
public RecordItem getRecordItemById(int id) {
for (IdSet idSet : idSets) {
for (RecordItem recordItem : idSet.recordItems) {
if (recordItem.getId() == id) {
return recordItem;
}
}
}
return null;
}
}

View File

@ -280,7 +280,7 @@
from
<include refid="Table_Name"/>
where
script_type in ('3','4')
script_type in ('3','4','6')
<if test="scriptIds!=null and scriptIds.size>0">
and id IN
<foreach collection="scriptIds" open="(" index="index" item="item" close=")" separator=",">

View File

@ -4,11 +4,15 @@ import com.alibaba.fastjson.JSON;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
import net.northking.cctp.upperComputer.automation.handler.AndroidAutomationHandler;
import net.northking.cctp.upperComputer.automation.handler.AutomationMessageHandler;
import net.northking.cctp.upperComputer.automation.handler.HarmonyAutomationHandler;
import net.northking.cctp.upperComputer.automation.handler.IosAutomationHandler;
import net.northking.cctp.upperComputer.deviceManager.AndroidDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.common.Python3;
import net.northking.cctp.upperComputer.driver.adb.AdbDevice;
import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCConnectStatus;
import net.northking.cctp.upperComputer.entity.PhoneEntity;
import net.northking.cctp.upperComputer.service.DeviceConnectionService;
import net.northking.cctp.upperComputer.utils.SpringUtils;
@ -72,6 +76,11 @@ public class AutomationWebSocketServer {
return;
}
logger.info("收到建立手机【{}】的自动化连接", serial);
boolean deviceIsOnline = checkMobileIsOnline(serial, type);
if (!deviceIsOnline) {
closeSession(session, "当前设备已经离线,无法创建自动化连接");
return;
}
boolean success = prepareEnvironment(serial,type,session);
if (!success) {
closeSession(session, "设备自动化连接建立失败,请检查设备是否正常");
@ -89,6 +98,8 @@ public class AutomationWebSocketServer {
return AndroidDeviceManager.getInstance().checkMobileIsOnline(serial);
} else if ("ios".equals(type)){
return IOSDeviceManager.getInstance().checkMobileIsOnline(serial);
}else if ("harmony".equals(type)){
return HarmonyDeviceManager.getInstance().checkMobileIsOnline(serial);
}
return false;
}
@ -186,6 +197,15 @@ public class AutomationWebSocketServer {
this.handler = new IosAutomationHandler(phoneEntity,session);
logger.info("手机[{}]初始化环境完成。。。。。。。", serial);
return true;
}else if ("harmony".equals(type)) {
HarmonyDevice harmonyDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(serial);
if (harmonyDevice == null || harmonyDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED) {
logger.warn("当前设备【{}】不在线",serial);
return false;
}
this.deviceId = serial;
this.handler = new HarmonyAutomationHandler(session,harmonyDevice);
return true;
}
return false;
}

View File

@ -5,8 +5,8 @@ package net.northking.cctp.upperComputer.automation.constants;
* @date : 2024/5/13 16:48
*/
public enum Command {
CLEAR("清空文本",20);
CLEAR("清空文本",20),
CLICK("点击", 21);
private String name;
private int code;

View File

@ -0,0 +1,48 @@
package net.northking.cctp.upperComputer.automation.entity;
/**
* @author : yineng.huang
* @date : 2024/10/30 16:43
*/
public class ScreenInfo {
private int width;
private int height;
private int rotation; //屏幕方向
private int scale = 1; //屏幕缩放比
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getRotation() {
return rotation;
}
public void setRotation(int rotation) {
this.rotation = rotation;
}
}

View File

@ -1,14 +1,37 @@
package net.northking.cctp.upperComputer.automation.handler;
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.northking.cctp.upperComputer.automation.constants.AutomationRequestCmd;
import net.northking.cctp.upperComputer.automation.constants.UpperParamKey;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import net.northking.cctp.upperComputer.automation.utils.CloudTestOcrHelper;
import net.northking.cctp.upperComputer.automation.utils.OcrHelper;
import net.northking.cctp.upperComputer.config.HttpRequestPathConfig;
import net.northking.cctp.upperComputer.config.MobileProperty;
import net.northking.cctp.upperComputer.driver.ios.command.data.DragXYData;
import net.northking.cctp.upperComputer.driver.ios.command.data.TapXYData;
import net.northking.cctp.upperComputer.entity.Attachment;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.utils.HttpUtils;
import net.northking.cctp.upperComputer.utils.SpringUtils;
import net.northking.cctp.upperComputer.utils.deviceHepler.DeviceHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.websocket.Session;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
public abstract class AbstractAutomationHandler implements AutomationMessageHandler{
@ -19,6 +42,16 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand
protected Session engineSession;
protected DeviceHelper deviceHandleHelper;
protected ScreenInfo screenInfo;
protected OcrHelper ocrHelper;
public AbstractAutomationHandler() {
ocrHelper = new CloudTestOcrHelper();
}
@Override
public void automationHandle(CmdAutomationRequest request, Session engineSession) {
String cmd = request.getCmd();
@ -106,4 +139,351 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand
}
}
protected Map<String, Object> getAppDetail(String appId) throws URISyntaxException {
Map<String, Object> data = null;
String path = SpringUtils.getProperties("nk.mobile-computer.publicFindAppInfo");
String server = SpringUtils.getProperties("nk.mobile-computer.serverAddr");
JSONObject result = HttpUtils.doGet(new URI(server + path + "?appId=" + appId), JSONObject.class);
logger.debug("查询app的结果{}", JSONObject.toJSONString(result));
if (result.getBoolean("success")) {
data = result.getJSONObject("data");
}
return data;
}
/**
* app
*
* @param appId
* @param appName
*/
protected boolean installApp(String appId, String appName) {
String appPath = "";
logger.info("Begin to download app[{}] to upperComputer...", appName);
try {
appPath = deviceHandleHelper.downloadAppToLocal(appId, appName);
} catch (Exception e) {
logger.error("下载app[{}]出错app名字{}", appId, appName, e);
return false;
}
if (StringUtils.isBlank(appPath)) {
logger.info("download app[{}] finish,but app is null ............", appName);
return false;
}
logger.info("download app[{}] finish,start install ............", appName);
boolean installSuccess = false;
try {
installSuccess = deviceHandleHelper.installApp(this.serial, appPath);
} catch (Exception e) {
logger.error("安装app[{}]出错app名字{}", appId, appName, e);
return false;
}
logger.info("App[{}] on device[{}] installed finish,success:{}", appName, this.serial, installSuccess);
return installSuccess;
}
protected String getFileBase64(File file) {
String encode = Base64.encode(file);
return encode;
}
protected String getOcrAreaToBase64(CmdAutomationRequest request) {
Map<String, Object> data = request.getData();
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
Integer cutWidth = (Integer) data.get(UpperParamKey.WIDTH);
Integer cutHeight = (Integer) data.get(UpperParamKey.HEIGHT);
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
logger.debug("拿到的截图参数----->>>>>x:{},y:{},screenWidth:{},screenHeight:{},cutWidth:{},cutHeight:{},查找的文本:{}", x, y, screenWidth, screenHeight, cutWidth, cutHeight, ocrText);
File file = deviceHandleHelper.getScreenShotFile(serial, x, y, cutWidth, cutHeight, screenWidth, screenHeight, screenInfo.getWidth(), screenInfo.getHeight());
logger.debug("执行步骤token{}ocr截图{}", request.getStepToken(), file.getAbsolutePath());
String base64Str = getFileBase64(file);
return base64Str;
}
protected String getClickTextOcrAreaToBase64(CmdAutomationRequest request) {
Map<String, Object> data = request.getData();
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
logger.debug("拿到的截图参数----->>>>>查找的文本:{}", ocrText);
File file = deviceHandleHelper.getScreenShotFile(serial);
if (null == file) {
logger.error("设备【{}】截图为空", serial);
throw new ExecuteException("设备截图失败");
}
logger.debug("执行步骤token{}ocr截图{}", request.getStepToken(), file.getAbsolutePath());
return getFileBase64(file);
}
protected TapXYData queryImageArea(CmdAutomationRequest request) {
TapXYData result = null;
Map<String, Object> data = request.getData();
String picName = (String) data.get(UpperParamKey.IMG_URL);
String resolution_s = (String) data.get(UpperParamKey.RESOLUTION_S);
String resolution_l = (screenInfo.getWidth() + "x" + screenInfo.getHeight());
logger.info("以图找图分辨率参数resolution_s:{}, resolution_l:{}", resolution_s, resolution_l);
File sourceFile = deviceHandleHelper.getScreenShotFile(serial); // 通过helper进行截图。
if (null == sourceFile) {
return result;
} else {
result = ocrHelper.doImgFindImg(sourceFile, resolution_s, resolution_l, picName, request.getStepToken());
}
return result;
}
protected TapXYData queryPoint(CmdAutomationRequest request) {
TapXYData tapXYData = null;
Map<String, Object> data = request.getData();
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
boolean overTurn = false;
if (screenWidth > screenHeight) {
overTurn = true;
}
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (overTurn) { //宽高反了的情况,调换
if (width < height) {
realityWidth = height;
realityHeight = width;
}
} else {
if (width > height) {
realityWidth = height;
realityHeight = width;
}
}
Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
if (realityX <= 0 || realityX >= realityWidth || realityY <= 0 || realityY >= realityHeight) {
logger.warn("步骤id【{}】坐标方式超出了屏幕范围", request.getStepToken());
}
int clickTrueX = new Double(realityX).intValue();
int clickTrueY = new Double(realityY).intValue();
tapXYData = new TapXYData();
tapXYData.setX(clickTrueX);
tapXYData.setY(clickTrueY);
logger.debug("实际点击的位置------->>>>>>x:{},y:{}", clickTrueX, clickTrueY);
return tapXYData;
}
public DragXYData calculateSwipePoint(String direction, Integer size) {
int width = screenInfo.getWidth() / screenInfo.getScale();
int height = screenInfo.getHeight() / screenInfo.getScale();
int x1, y1, x2, y2;
if ("up".equalsIgnoreCase(direction)) {
x1 = x2 = width / 2;
y1 = height * 7 / 10;
if (null == size || size <= 0) {
y2 = y1 - height / 2;
} else {
y2 = y1 - size;
}
} else if ("down".equalsIgnoreCase(direction)) {
x1 = x2 = width / 2;
y1 = height * 3 / 10;
if (null == size || size <= 0) {
y2 = y1 + height / 2;
} else {
y2 = y1 + size;
}
} else if ("left".equalsIgnoreCase(direction)) {
y1 = y2 = height / 2;
x1 = width * 7 / 10;
if (null == size || size <= 0) {
x2 = x1 - width / 2;
} else {
x2 = x1 - size;
}
} else if ("right".equalsIgnoreCase(direction)) {
y1 = y2 = height / 2;
x1 = width * 3 / 10;
if (null == size || size <= 0) {
x2 = x1 + width / 2;
} else {
x2 = x1 + size;
}
} else {
throw new ExecuteException("不支持的滑动方向");
}
DragXYData dragXYData = new DragXYData();
dragXYData.setX(x1);
dragXYData.setToX(x2);
dragXYData.setY(y1);
dragXYData.setToY(y2);
dragXYData.setDuration(0);
return dragXYData;
}
@Override
public void handleApp(CmdAutomationRequest request) {
Map<String, Object> data = request.getData();
String appPackage = (String) data.get(UpperParamKey.APP_PACKAGE);
String type = (String) data.get(UpperParamKey.TYPE);
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "app处理失败").withData(false);
IosDebuggerServiceImpl iosService = SpringUtils.getBean(IosDebuggerServiceImpl.class);
try {
if ("0".equalsIgnoreCase(type)) {
if (!deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
response = CmdAutomationResponse.builderFailure(request, "app未安装");
return;
}
logger.info("关闭harmony的app:{}", appPackage);
boolean success = deviceHandleHelper.terminateApp(serial, appPackage);
if (!success) {
response = CmdAutomationResponse.builderFailure(request, "app关闭失败").withData(false);
} else {
response = CmdAutomationResponse.builderSuccess(request, "app关闭成功").withData(true);
}
} else if ("1".equalsIgnoreCase(type)) {
logger.info("启动harmony的app:{}", appPackage);
if (!deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
response = CmdAutomationResponse.builderFailure(request, "app未安装");
return;
}
boolean success = false;
int count = 0;
do {
success = deviceHandleHelper.activateApp(this.serial, appPackage);
if (!success) {
logger.info("app启动失败尝试重试启动第 {} 次", ++count);
}
} while (!success && count < 5);
if (!success) {
response = CmdAutomationResponse.builderFailure(request, "app启动失败").withData(false);
} else {
response = CmdAutomationResponse.builderSuccess(request, "app启动成功").withData(true);
}
} else {
logger.info("重启harmony的app:{}", appPackage);
if (!deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
response = CmdAutomationResponse.builderFailure(request, "app未安装");
return;
}
boolean success = deviceHandleHelper.terminateApp(serial, appPackage);
if (!success) {
response = CmdAutomationResponse.builderFailure(request, "app关闭失败").withData(false);
}
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
logger.error(ie.getMessage());
}
boolean successActive = deviceHandleHelper.activateApp(this.serial, appPackage);
if (!successActive) {
response = CmdAutomationResponse.builderFailure(request, "app启动失败").withData(false);
} else {
response = CmdAutomationResponse.builderSuccess(request, "app启动成功").withData(true);
}
}
} catch (Exception e) {
logger.error("处理app失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
public abstract void initScreenData();
/**
*
* @param originFile
* @param x
* @param y
* @return
*/
protected File addSignatureToImage(File originFile, Integer x, Integer y) {
if (x == null || y == null) {
return originFile;
}
if (!originFile.exists()) {
logger.info("截图文件不存在:{}", originFile.getAbsolutePath());
return originFile;
}
String fullName = originFile.getName();
String suffix = fullName.substring(fullName.lastIndexOf(".") + 1);
String name = fullName.substring(0, fullName.lastIndexOf("."));
File targetFile = new File(originFile.getParentFile().getAbsolutePath() + File.separator + name + "_with_dot" + "." + suffix);
try {
BufferedImage bi = ImageIO.read(originFile);
Graphics2D g2d = bi.createGraphics(); // 生成画布
g2d.setColor(Color.RED); // 红色
g2d.fillOval(x, y, 20, 20); // 在图片的xy上画上一个直径20的实心原点
g2d.dispose();
ImageIO.write(bi, suffix, targetFile);
} catch (IOException e) {
logger.error("io异常", e);
} catch (Exception e) {
logger.error("添加红点失败", e);
}
if (!targetFile.exists()) {
targetFile = originFile;
}
return targetFile;
}
protected String doUploadExecuteScreenShotToServer(String tenantId, String absolutePath) {
String path = null;
MobileProperty mobileProperty = SpringUtils.getBean(MobileProperty.class);
String serverAddr = mobileProperty.getServerAddr();
String publicUploadAddr = mobileProperty.getPublicUploadAddr();
Attachment upload = HttpUtils.upload(serverAddr + publicUploadAddr, absolutePath, tenantId);
if (null != upload && org.apache.commons.lang3.StringUtils.isNotBlank(upload.getId())) {
path = upload.getUrlPath();
logger.debug("文件上传成功返回id{},path:{}", upload.getId(),path);
}
return path;
}
@Override
public void screenShot(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "屏幕截图失败");
logger.debug("开始上传截图,信息:{}", JSON.toJSONString(request));
try {
Map<String, Object> data = request.getData();
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
File file = deviceHandleHelper.getScreenShotFile(serial);
File addSignatureToImage = addSignatureToImage(file, x, y);
String path = null;
try {
//租户id
String tenantId = (String) data.get(UpperParamKey.TENANT_ID);
path = doUploadExecuteScreenShotToServer(tenantId, addSignatureToImage.getAbsolutePath());
} catch (Exception e) {
logger.error("截图失败", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
if (file.exists()) {
boolean delete = file.delete();
if (!delete) {
logger.warn("临时文件【{}】删除失败", file.getAbsolutePath());
}
}
if (addSignatureToImage.exists()) {
boolean delete = addSignatureToImage.delete();
if (!delete) {
logger.warn("临时文件【{}】删除失败", addSignatureToImage.getAbsolutePath());
}
}
}
if (StringUtils.isNotBlank(path)) {
response = CmdAutomationResponse.builderSuccess(request, "屏幕截图成功").withData(path);
}
} catch (Exception e) {
logger.error("屏幕截图失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
}

View File

@ -28,6 +28,7 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{
private AndroidAgentSession syncAgentSession; //同步处理设备指令的AgentSession
public AndroidAutomationHandler(AdbDevice currentDevice,Session session) throws IOException {
super();
this.adbDevice = currentDevice;
this.serial = currentDevice.getSerial();
this.engineSession = session;
@ -170,6 +171,11 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{
}
@Override
public void initScreenData() {
}
@Override
public void pressHomeKey(CmdAutomationRequest request) {

View File

@ -0,0 +1,939 @@
package net.northking.cctp.upperComputer.automation.handler;
import net.northking.cctp.upperComputer.automation.constants.Command;
import net.northking.cctp.upperComputer.automation.constants.UpperParamKey;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import net.northking.cctp.upperComputer.constants.HarmonyKeyBoardCodeEnum;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.thread.HarmonyProvider;
import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize;
import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent;
import net.northking.cctp.upperComputer.driver.ios.command.data.DragXYData;
import net.northking.cctp.upperComputer.driver.ios.command.data.TapXYData;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.utils.deviceHepler.harmony.HarmonyHandlerHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.websocket.Session;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author : yineng.huang
* @date : 2024/10/29 17:26
*/
public class HarmonyAutomationHandler extends AbstractAutomationHandler {
private final Logger logger = LoggerFactory.getLogger(HarmonyAutomationHandler.class);
private HarmonyDevice harmonyDevice;
private XPath xpath;
private DocumentBuilder builder;
public HarmonyAutomationHandler(Session session, HarmonyDevice device) {
super();
this.engineSession = session;
this.harmonyDevice = device;
this.serial = device.getHdcDevice().getConnectKey();
this.deviceHandleHelper = new HarmonyHandlerHelper();
initScreenData();
XPathFactory xpathFactory = XPathFactory.newInstance();
xpath = xpathFactory.newXPath();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
logger.error("Document创建失败", e);
throw new ExecuteException("无法创建自动化环境Xpath构建工具无法初始化");
}
}
@Override
public void destroy() {
}
@Override
public void openMobileNotificationBar(CmdAutomationRequest request) {
}
@Override
public void closeMobileNotificationBar(CmdAutomationRequest request) {
}
@Override
public void initApplication(CmdAutomationRequest request) {
CmdAutomationResponse response = null;
try {
Map<String, Object> initData = request.getData();
boolean reInstall = false;
if (null != initData.get("forceReInstall")) {
reInstall = (boolean) initData.get("forceReInstall");
}
String appId = (String) initData.get("appId");
Map<String, Object> appDetail = null;
try {
appDetail = getAppDetail(appId);
} catch (Exception e) {
logger.error("查询app信息出错原因", e);
response = CmdAutomationResponse.builderFailure(request, "查询app信息出错");
return;
}
if (null == appDetail) {
response = CmdAutomationResponse.builderFailure(request, "app应用已被删除无法初始化");
return;
}
String appUrl = (String) appDetail.get("appUrl");
String appPackage = (String) appDetail.get("packageName");
String appVersion = (String) appDetail.get("buildVersion");
String appName = appUrl.substring(appUrl.lastIndexOf("_") + 1);
if (reInstall) {
if (deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
logger.info("Begin to uninstall app[{}] from device[{}]...", appPackage, this.serial);
deviceHandleHelper.removeApp(this.serial, appPackage);
logger.info("App[{}] from device[{}] uninstalled.", appPackage, this.serial);
} else {
logger.info("App[{}] on device[{}] is not installed.", appPackage, this.serial);
}
}
if (!deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
installApp(appId, appName);
} else {
logger.info("device[{}] already install app[{}]...", this.serial, appPackage);
String oldPackageCode = deviceHandleHelper.getOldPackageCode(this.serial, appPackage);
if (!appVersion.equalsIgnoreCase(oldPackageCode)) {
logger.info("install app version[{}],app in device version[{}] ===========> reInstall", appVersion, oldPackageCode);
deviceHandleHelper.removeApp(this.serial, appPackage);
logger.info("app version[{}] remove from device[{}] successfully", appVersion, this.serial);
installApp(appId, appName);
} else {
logger.info("App[{}] on device[{}] already exists ===========> terminate", appPackage, this.serial);
}
}
if (!deviceHandleHelper.isAppInstalled(this.serial, appPackage)) {
response = CmdAutomationResponse.builderFailure(request, "app安装失败");
return;
}
logger.info("activate app[{}]", appPackage);
boolean success = deviceHandleHelper.activateApp(this.serial, appPackage);
if (!success) {
response = CmdAutomationResponse.builderFailure(request, "应用启动失败");
} else {
response = CmdAutomationResponse.builderSuccess(request, "app初始化成功");
}
} catch (Exception e) {
logger.error("设备【{}】应用初始化失败", this.serial, e);
CmdAutomationResponse.builderFailure(request, "应用初始化失败");
} finally {
sendResultToEngine(response);
}
logger.info("自动化执行环境【device:{}】准备就绪", this.serial);
}
@Override
public void clickPoint(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "点击失败").withData(false);
Map<String, Object> data = request.getData();
try {
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
TapXYData tapXYData = new TapXYData();
tapXYData.setX(x);
tapXYData.setY(y);
click(tapXYData);
response = CmdAutomationResponse.builderSuccess(request, "点击成功").withData(true);
} catch (Exception e) {
logger.error("输入失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void searchNodeByNodeTree(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "查找控件完成");
Map<String, Object> data = request.getData();
try {
TapXYData xyData = findNodeByXml(request);
if (null == xyData) {
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
}else {
response = CmdAutomationResponse.builderSuccess(request, "查找控件完成").withData(xyData);
}
} catch (Exception e) {
logger.error("根据xpath判断元素是否存在失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
logger.info("根据nodetree查找元素失败的response{}", response);
sendResultToEngine(response);
}
}
@Override
public void searchNodeByPoint(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
try {
Map<String, Object> data = request.getData();
TapXYData tapXYData = null;
tapXYData = queryPoint(request);
if (null == tapXYData) {
response = CmdAutomationResponse.builderFailure(request, "查找控件失败").withData(false);
} else {
tapXYData.setX(tapXYData.getX());
tapXYData.setY(tapXYData.getY());
//转换比例
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
Double realityX = (double) tapXYData.getX() / screenInfo.getWidth() * realityWidth;
Double realityY = (double) tapXYData.getY() / screenInfo.getHeight() * realityHeight;
tapXYData.setX(realityX.intValue());
tapXYData.setY(realityY.intValue());
response = CmdAutomationResponse.builderSuccess(request, "查找控件完成").withData(tapXYData);
}
} catch (Exception e) {
logger.error("根据坐标是否存在失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void searchNodeByOcr(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
try {
TapXYData tapXYData = queryClickTextOcrArea(request);
if (null == tapXYData) {
response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
} else {
//转换比例
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
Double realityX = (double) tapXYData.getX() / screenInfo.getWidth() * realityWidth;
Double realityY = (double) tapXYData.getY() / screenInfo.getHeight() * realityHeight;
tapXYData.setX(realityX.intValue());
tapXYData.setY(realityY.intValue());
response = CmdAutomationResponse.builderSuccess(request, "查找控件完成").withData(tapXYData);
}
} catch (Exception e) {
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void searchNodeByImage(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
try {
Map<String, Object> data = request.getData();
Integer waitTimeout = (Integer) data.get(UpperParamKey.WAIT_TIMEOUT);
Integer times = (Integer) data.get(UpperParamKey.TIMES);
TapXYData tapXYData = null;
if (null == times) {
long startTime = System.currentTimeMillis(); //当前时间
long elapsedTime = 0; // 初始化经过的时间
while (elapsedTime < waitTimeout * 1000) {
tapXYData = queryImageArea(request);
if (tapXYData != null) {
break;
} else {
try {
if (System.currentTimeMillis() - startTime + 2000 < waitTimeout * 1000) {
Thread.sleep(2000);
}
} catch (InterruptedException e) {
logger.warn("查找控件失败", e);
}
}
long currentTime = System.currentTimeMillis();
elapsedTime = currentTime - startTime;
}
} else {
tapXYData = queryImageArea(request);
}
if (null == tapXYData) {
response = CmdAutomationResponse.builderFailure(request, "未查找到控件");
} else {
//转换比例
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
Double realityX = (double) tapXYData.getX() / screenInfo.getWidth() * realityWidth;
Double realityY = (double) tapXYData.getY() / screenInfo.getHeight() * realityHeight;
tapXYData.setX(realityX.intValue());
tapXYData.setY(realityY.intValue());
response = CmdAutomationResponse.builderSuccess(request, "查找控件完成").withData(tapXYData);
}
} catch (Exception e) {
logger.error("根据图像是否存在失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void getElementValueByNode(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "未查到控件");
try {
NodeList nodeList = findElementByXml(request);
if (null != nodeList && nodeList.getLength() > 0) {
Element item = (Element) nodeList.item(0);
String value = item.getAttribute("text");
if (StringUtils.isNotBlank(value)) {
response = CmdAutomationResponse.builderSuccess(request, "已获取控件的值").withData(value);
} else {
response = CmdAutomationResponse.builderFailure(request, "无法获取控件的值");
}
} else {
response = CmdAutomationResponse.builderFailure(request, "找不到控件");
}
} catch (Exception e) {
logger.error("根据xpath获取控件的值失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void getElementValueByOcr(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr获取控件值失败");
try {
String value = null;
Map<String, Object> requestData = request.getData();
String targetBase64 = "";
if (requestData.get(UpperParamKey.X) != null) {
targetBase64 = getOcrAreaToBase64(request);
} else {
targetBase64 = getClickTextOcrAreaToBase64(request);
}
if (StringUtils.isBlank(targetBase64)) {
response = CmdAutomationResponse.builderFailure(request, "未获取到ocr区域");
return;
}
value = ocrHelper.getTextValue(targetBase64);
logger.info("识别的图片文字为:{}", value);
if (StringUtils.isNotBlank(value)) {
response = CmdAutomationResponse.builderSuccess(request, "根据ocr获取控件值成功").withData(value);
} else {
response = CmdAutomationResponse.builderFailure(request, "根据ocr获取控件值失败");
}
} catch (Exception e) {
logger.error("根据ocr获取控件的值失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void getVerificationCodeByNode(CmdAutomationRequest request) {
}
@Override
public void getVerificationCodeByOcr(CmdAutomationRequest request) {
}
@Override
public void longPress(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "长按失败").withData(false);
Map<String, Object> data = request.getData();
try {
HarmonyProvider harmonyProvider = getHarmonyProvider();
if (null == harmonyProvider) {
response = CmdAutomationResponse.builderFailure(request, "设备与上位机连接断开");
return;
}
int x = (int) data.get(UpperParamKey.X);
int y = (int) data.get(UpperParamKey.Y);
logger.debug("步骤【{}】长按的位置----->x:{},y:{}", request.getStepToken(), x, y);
boolean success = harmonyProvider.touchDown(x, y);
Thread.sleep(2000);
success = harmonyProvider.touchUp(x, y);
if (success) {
response = CmdAutomationResponse.builderSuccess(request, "长按成功").withData(success);
} else {
response = CmdAutomationResponse.builderFailure(request, "长按失败");
}
} catch (Exception e) {
logger.error("输入失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void swipe(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "滑动失败");
try {
Map<String, Object> data = request.getData();
String direction = (String) data.get(UpperParamKey.SWIPE_DIRECTION);
Integer size = (Integer) data.get(UpperParamKey.SWIPE_SIZE);
try {
boolean success = swipeScreen(direction, size);
if (success) {
response = CmdAutomationResponse.builderSuccess(request, "滑动成功").withData(success);
} else {
response = CmdAutomationResponse.builderFailure(request, "滑动失败");
}
} catch (Exception e) {
logger.error("滑动失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
}
} finally {
sendResultToEngine(response);
}
}
@Override
public void inputText(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "输入失败").withData(false);
Map<String, Object> data = request.getData();
try {
boolean clear = (boolean) data.get(UpperParamKey.CLEAR);
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
if (clear) {
String xpath = (String) data.get(UpperParamKey.NODE_TREE);
logger.debug("步骤【{}】输入前清空数据xpath:{}", request.getStepToken(), xpath);
TapXYData tapXYData = new TapXYData();
tapXYData.setX(x);
tapXYData.setY(y);
click(tapXYData);
HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(this.serial);
ArrayList<Integer> keyInt = new ArrayList<>();
keyInt.add(HarmonyKeyBoardCodeEnum.KEYCODE_CTRL_LEFT.getCode());
keyInt.add(HarmonyKeyBoardCodeEnum.KEYCODE_A.getCode());
boolean selectAll = currentDevice.keyEvent(keyInt);//ctrl+a 退格
if (selectAll) {
logger.debug("步骤【{}】输入前清空数据已全选", request.getStepToken());
} else {
logger.debug("步骤【{}】输入前清空数据已全选失败", request.getStepToken());
}
List<Integer> delete = new ArrayList<>();
delete.add(HarmonyKeyBoardCodeEnum.KEYCODE_DEL.getCode());
boolean deleteAll = currentDevice.keyEvent(delete);//ctrl+a 退格
if (deleteAll) {
logger.debug("步骤【{}】输入前清空数据已清空", request.getStepToken());
} else {
logger.debug("步骤【{}】输入前清空数据未清空",request.getStepToken());
}
}
String inputText = (String) data.get(UpperParamKey.INPUT_TEXT);
if (StringUtils.isBlank(inputText)) {
response = CmdAutomationResponse.builderFailure(request, "输入的文本不能为空");
return;
}
logger.debug("步骤【{}】即将输入文本:{}", request.getStepToken(), inputText);
HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(this.serial);
if (null != currentDevice) {
boolean success= currentDevice.inputText(x, y, inputText);
if (success) {
response = CmdAutomationResponse.builderSuccess(request, "输入成功").withData(success);
} else {
response = CmdAutomationResponse.builderFailure(request, "输入失败").withData(success);
}
} else {
response = CmdAutomationResponse.builderFailure(request, "设备已经掉线,无法输入");
}
} catch (Exception e) {
logger.error("输入失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void handleNodeByNodeTree(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据node节点点击控件失败").withData(false);
try {
Map<String, Object> data = request.getData();
Integer commandId = (Integer) data.get(UpperParamKey.COMMAND_ID);
TapXYData xyData = findNodeByXml(request);
if (null == xyData) {
response = CmdAutomationResponse.builderFailure(request, "控件不存在");
return;
}
click(xyData);
response = CmdAutomationResponse.builderSuccess(request, "点击成功").withData(true);
} catch (Exception e) {
logger.error("根据node节点点击控件失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void handleNodeByPoint(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据坐标点击控件失败").withData(false);
try {
Map<String, Object> data = request.getData();
TapXYData tapXYData = queryPoint(request);
if (null == tapXYData) {
response = CmdAutomationResponse.builderFailure(request, "未查找到控件").withData(false);
return;
}
int commandId = (int) data.get(UpperParamKey.COMMAND_ID);
if (Command.CLICK.getCode() == commandId) { //点击
click(tapXYData);
response = CmdAutomationResponse.builderSuccess(request, "点击控件成功").withData(true);
}
} catch (Exception e) {
logger.error("根据坐标点击控件失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void handleNodeByOcr(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr点击控件失败").withData(false);
try {
Map<String, Object> data = request.getData();
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
if (StringUtils.isBlank(ocrText)) {
response = CmdAutomationResponse.builderFailure(request, "ocr查找的文本不能为空");
return;
}
TapXYData point = queryClickTextOcrArea(request);
if (null == point) {
response = CmdAutomationResponse.builderFailure(request, "未查找到控件").withData(false);
return;
}
int commandId = (int) data.get(UpperParamKey.COMMAND_ID);
if (Command.CLICK.getCode() == commandId) { //点击
click(point);
response = CmdAutomationResponse.builderSuccess(request, "点击控件成功").withData(true);
}
} catch (Exception e) {
logger.error("根据ocr点击控件失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void handleNodeByImage(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据图像点击控件失败").withData(false);
try {
Map<String, Object> data = request.getData();
// Integer waitTimeout = (Integer) data.get(UpperParamKey.WAIT_TIMEOUT);
Integer waitTimeout = 120;
TapXYData tapXYData = null;
long startTime = System.currentTimeMillis(); //当前时间
long elapsedTime = 0; // 初始化经过的时间
while (elapsedTime < waitTimeout * 1000) {
tapXYData = queryImageArea(request);
long currentTime = System.currentTimeMillis();
elapsedTime = currentTime - startTime;
if (tapXYData != null) {
break;
} else {
try {
if (System.currentTimeMillis() - startTime + 2000 < waitTimeout * 1000) {
Thread.sleep(2000);
}
} catch (InterruptedException e) {
logger.warn("根据图像点击控件失败", e);
}
}
}
if (null == tapXYData) {
response = CmdAutomationResponse.builderFailure(request, "未找到元素").withData(false);
return;
}
int commandId = (int) data.get(UpperParamKey.COMMAND_ID);
if (Command.CLICK.getCode() == commandId) { //点击
click(tapXYData);
response = CmdAutomationResponse.builderSuccess(request, "点击控件成功").withData(true);
}
} catch (Exception e) {
logger.error("根据图像点击控件失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void clickTextByOcr(CmdAutomationRequest request) {
}
@Override
public void clickTextByOcrNew(CmdAutomationRequest request) {
}
@Override
public void ifText(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr是否存在文本失败").withData(false);
try {
Map<String, Object> data = request.getData();
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
if (StringUtils.isBlank(ocrText)) {
response = CmdAutomationResponse.builderFailure(request, "ocr查找的文本不能为空");
return;
}
TapXYData tapXYData = queryClickTextOcrArea(request);
if (tapXYData == null) {
// 执行成功,但找不到文本
response = CmdAutomationResponse.builderFailure(request, "未查找到文本");
} else {
// 转换比例
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
Double realityX = (double) tapXYData.getX() / screenInfo.getWidth() * realityWidth;
Double realityY = (double) tapXYData.getY() / screenInfo.getHeight() * realityHeight;
tapXYData.setX(realityX.intValue());
tapXYData.setY(realityY.intValue());
// 执行成功,找到文本
response = CmdAutomationResponse.builderSuccess(request, "存在文本");
}
response.withData(tapXYData);
} catch (Exception e) {
// 执行失败
logger.error("根据ocr是否存在失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void pressHomeKey(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "点击home键失败").withData(false);
try {
HarmonyProvider harmonyProvider = getHarmonyProvider();
boolean success = harmonyProvider.pressHome();
if (success) {
response = CmdAutomationResponse.builderSuccess(request, "点击home键成功").withData(true);
} else {
response = CmdAutomationResponse.builderFailure(request, "点击home键失败");
}
} catch (Exception e) {
logger.error("点击home键失败原因", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
@Override
public void pressAndSwipe(CmdAutomationRequest request) {
}
@Override
public void getTextDirectionText(CmdAutomationRequest request) {
}
@Override
public void inputPasswordByOcr(CmdAutomationRequest request) {
}
@Override
public void getElementMoneyText(CmdAutomationRequest request) {
}
@Override
public void getElementValueByPathOcr(CmdAutomationRequest request) {
}
@Override
public void swipeForSwipeToFindTargetElement(CmdAutomationRequest request) {
}
@Override
public void elementSwipe(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "控件滑动失败");
try {
Map<String, Object> data = request.getData();
String direction = (String) data.get(UpperParamKey.SWIPE_DIRECTION);
Integer size = (Integer) data.get(UpperParamKey.SWIPE_SIZE);
Integer x1 = (Integer) data.get(UpperParamKey.X);
Integer y1 = (Integer) data.get(UpperParamKey.Y);
int x2, y2;
if ("up".equalsIgnoreCase(direction)) {
x2 = x1;
y2 = y1 - size;
} else if ("down".equalsIgnoreCase(direction)) {
x2 = x1;
y2 = y1 + size;
} else if ("left".equalsIgnoreCase(direction)) {
x2 = x1 - size;
y2 = y1;
} else if ("right".equalsIgnoreCase(direction)) {
x2 = x1 + size;
y2 = y1;
} else {
response = CmdAutomationResponse.builderFailure(request, "只支持上下左右滑动");
return;
}
HarmonyProvider harmonyProvider = getHarmonyProvider();
if (null == harmonyProvider) {
response = CmdAutomationResponse.builderFailure(request, "设备已经掉线");
return;
}
boolean result = harmonyProvider.touchDown(x1, y1);
result = harmonyProvider.touchMove(x2, y2);
result = harmonyProvider.touchUp(x2, y2);
if (result) {
response = CmdAutomationResponse.builderSuccess(request, "控件滑动成功").withData(result);
} else {
response = CmdAutomationResponse.builderFailure(request, "控件滑动失败");
}
} catch (Exception e) {
logger.error("控件滑动失败,原因:", e);
response = CmdAutomationResponse.builderFailure(request, e.getMessage());
} finally {
sendResultToEngine(response);
}
}
private TapXYData queryClickTextOcrArea(CmdAutomationRequest request) {
TapXYData result = null;
Map<String, Object> data = request.getData();
String base64Str = "";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
Integer diffussion = data.get(UpperParamKey.DIFFUSSION) != null ? (Integer) data.get(UpperParamKey.DIFFUSSION) : null;
Integer index = (Integer) data.get(UpperParamKey.INDEX);
double realityX = 0.0;
double realityY = 0.0;
if (data.get(UpperParamKey.X) != null) {
base64Str = getOcrAreaToBase64(request);
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
//转换比例
int width = screenInfo.getWidth();
int height = screenInfo.getHeight();
int realityWidth = width;
int realityHeight = height;
if (width > height) { //宽高反了的情况,调换
realityWidth = height;
realityHeight = width;
}
realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
} else {
base64Str = getClickTextOcrAreaToBase64(request);
}
if (StringUtils.isNotBlank(base64Str)) {
result = ocrHelper.doFindTextByOcr(base64Str, ocrText, diffussion, index, realityX, realityY);
}
return result;
}
@Override
public void initScreenData() {
this.screenInfo = new ScreenInfo();
HarmonyProvider harmonyProvider = getHarmonyProvider();
DisplaySize screenSize = harmonyProvider.getScreenSize();
if (null == screenSize) {
throw new ExecuteException("获取当前手机的屏幕信息失败");
}
screenInfo.setHeight(screenSize.height);
screenInfo.setWidth(screenSize.width);
logger.info("当前手机【{}】的宽:{},高:{}", this.serial, screenInfo.getWidth(), screenInfo.getHeight());
}
private HarmonyProvider getHarmonyProvider() {
HarmonyProvider provider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(this.serial);
if (null == provider) {
throw new ExecuteException("当前设备已经掉线,请检查设备");
}
return provider;
}
private void click(TapXYData point) {
logger.debug("设备【{}】实际点击的位置------->>>>>>x:{},y:{}", this.serial, point.getX(), point.getY());
HarmonyProvider harmonyProvider = getHarmonyProvider();
harmonyProvider.touchDown(point.getX(), point.getY());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
harmonyProvider.touchUp(point.getX(), point.getY());
logger.debug("设备【{}】点击操作完成.........", this.serial);
}
private boolean swipeScreen(String direction, Integer size) {
DragXYData dragXYData = calculateSwipePoint(direction, size);
HarmonyProvider harmonyProvider = getHarmonyProvider();
harmonyProvider.touchDown(dragXYData.x, dragXYData.y);
harmonyProvider.touchMove(dragXYData.toX, dragXYData.toY);
harmonyProvider.touchUp(dragXYData.toX, dragXYData.toY);
logger.debug("设备【{}】滑动完毕.............", this.serial);
return true;
}
private NodeList findNode(String xpathText, Document document) throws XPathExpressionException {
XPathExpression expression = xpath.compile(xpathText);
NodeList nodeList = (NodeList) expression.evaluate(document, XPathConstants.NODESET);
System.out.println("查找XPath" + xpathText);
System.out.println("找到以下节点:");
for (int i = 0; i < nodeList.getLength(); i++) {
Element node = (Element) nodeList.item(i);
System.out.println("<" + node.getNodeName() + " x=" + node.getAttribute("x") + " y=" + node.getAttribute("y") + " width=" + node.getAttribute("width") + " text=" + node.getAttribute("text") + ">");
}
return nodeList;
}
private TapXYData findNodeByXml(CmdAutomationRequest request){
NodeList nodeList = findElementByXml(request);
if (null == nodeList || nodeList.getLength() <= 0 ) {
return null;
} else {
TapXYData tapXYData = new TapXYData();
Element item = (Element) nodeList.item(0);
Integer width = Integer.parseInt(item.getAttribute("width"));
Integer height = Integer.parseInt(item.getAttribute("height"));
Integer realityX = Integer.parseInt(item.getAttribute("x"));
Integer realityY = Integer.parseInt(item.getAttribute("y"));
tapXYData.setX((realityX + width / 2));
tapXYData.setY((realityY + height / 2));
return tapXYData;
}
}
private NodeList findElementByXml(CmdAutomationRequest request){
Map<String, Object> data = request.getData();
String nodeTree = (String) data.get(UpperParamKey.NODE_TREE);
Integer waitTimeOut = (Integer) data.get(UpperParamKey.WAIT_TIMEOUT);
String swipe = (String) data.get(UpperParamKey.IS_SWIPE);
Integer swipeCount = (Integer) data.get(UpperParamKey.SWIPE_COUNT);
logger.debug("拿到的nodeTree:{}", nodeTree);
long startTime = System.currentTimeMillis();
long endTime = startTime;
if ((swipeCount <= 0 || swipeCount > 5) || "0".equals(swipe)) { //超过5次不给滑或者选择不滑屏
swipeCount = 0;
}
int perSwipeTime = waitTimeOut;
if (swipeCount > 0) {
perSwipeTime = waitTimeOut / swipeCount;
if (perSwipeTime <= 0) { //防止每次超时时间过小
perSwipeTime = 1;
}
}
logger.debug("步骤:{}开始查找控件,滑屏:{},滑动次数:{},每次查找超时时间:{},总超时时间:{}", request.getStepToken(), swipe, swipeCount, perSwipeTime, waitTimeOut);
boolean alreadyFind = false;
int screenIndex = 1;
NodeList nodeList = null;
while (!alreadyFind) {
int findIndex = 1;
do {
logger.debug("步骤:{},第{}屏,第{}次开始查找元素", request.getStepToken(), screenIndex, findIndex);
HarmonyProvider harmonyProvider = getHarmonyProvider();
UiComponent uiComponent = harmonyProvider.captureLayout();
Document document = builder.newDocument();
Node xmlElement = uiComponent.createXMLElement(document);
document.appendChild(xmlElement);
try {
nodeList = findNode(nodeTree, document);
} catch (XPathExpressionException e) {
logger.error("步骤:{}xpath格式不对{}",request.getStepToken(),nodeTree);
return null;
}
endTime = System.currentTimeMillis();
logger.debug("步骤:{},第{}次查找元素的总时间(累加):{}ms结果nodeList是否是空{}", request.getStepToken(), findIndex, endTime - startTime, null == nodeList);
findIndex++;
if (null != nodeList && nodeList.getLength() > 0) {
alreadyFind = true;
break;
}
} while ((endTime - startTime) < perSwipeTime * 1000);
logger.debug("步骤:{},第{}屏,查找的结果:{},nodeList是否是空{}", request.getStepToken(), screenIndex, alreadyFind, null == nodeList);
if (alreadyFind) { //找到了,不用找了
logger.debug("步骤:{},第{}屏,找到了,不找了", request.getStepToken(), screenIndex);
break;
} else { //没找到,滑不滑动屏幕
swipeCount--;
if (swipeCount < 0) {
logger.debug("步骤:{},第{}屏,没找到元素,不滑屏了", request.getStepToken(), screenIndex);
break;
} else {
logger.debug("步骤:{},第{}屏,没找到元素,滑一下再继续找", request.getStepToken(), screenIndex);
boolean success = swipeScreen(swipe, 500);//固定的滑动方向滑动的commandId为24
if (success) {
logger.debug("步骤:{},第{}次滑动:{}屏幕成功", request.getStepToken(), screenIndex, swipe);
} else {
logger.debug("步骤:{},第{}次滑动:{}屏幕失败", request.getStepToken(), screenIndex, swipe);
}
screenIndex++;
}
}
}
return nodeList;
}
}

View File

@ -9,6 +9,7 @@ import net.northking.cctp.upperComputer.automation.constants.Command;
import net.northking.cctp.upperComputer.automation.constants.UpperParamKey;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import net.northking.cctp.upperComputer.constants.HandCommand;
import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager;
@ -23,10 +24,10 @@ import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.service.IosDebuggerServiceImpl;
import net.northking.cctp.upperComputer.utils.HttpUtils;
import net.northking.cctp.upperComputer.utils.SpringUtils;
import net.northking.cctp.upperComputer.utils.deviceHepler.ios.IosDeviceHandleHelper;
import net.northking.cctp.upperComputer.utils.deviceHepler.ios.LinuxAndWindowsIosHandleHelper;
import net.northking.cctp.upperComputer.utils.deviceHepler.ios.MacIosHandleHelper;
import net.northking.cctp.upperComputer.utils.hzBank.HzBankOcrUtils;
import net.northking.cctp.upperComputer.utils.ios.IosDeviceHandleHelper;
import net.northking.cctp.upperComputer.utils.ios.LinuxAndWindowsIosHandleHelper;
import net.northking.cctp.upperComputer.utils.ios.MacIosHandleHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,9 +60,8 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
private PhoneEntity phoneEntity;
private IosDeviceHandleHelper deviceHandleHelper;
public IosAutomationHandler(PhoneEntity phoneEntity, Session session) {
super();
this.phoneEntity = phoneEntity;
this.serial = phoneEntity.getUdid();
this.engineSession = session;
@ -572,31 +572,9 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
private String getOcrAreaToBase64(CmdAutomationRequest request) {
Map<String, Object> data = request.getData();
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
Integer cutWidth = (Integer) data.get(UpperParamKey.WIDTH);
Integer cutHeight = (Integer) data.get(UpperParamKey.HEIGHT);
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
logger.debug("拿到的截图参数----->>>>>x:{},y:{},screenWidth:{},screenHeight:{},cutWidth:{},cutHeight:{},查找的文本:{}", x, y, screenWidth, screenHeight, cutWidth, cutHeight, ocrText);
File file = deviceHandleHelper.getScreenShotFile(serial, x, y, cutWidth, cutHeight, screenWidth, screenHeight);
logger.debug("执行步骤token{}ocr截图{}", request.getStepToken(), file.getAbsolutePath());
String base64Str = getFileBase64(file);
return base64Str;
}
private String getClickTextOcrAreaToBase64(CmdAutomationRequest request) {
Map<String, Object> data = request.getData();
String ocrText = (String) data.get(UpperParamKey.OCR_TEXT);
logger.debug("拿到的截图参数----->>>>>查找的文本:{}", ocrText);
// File file = ScreenShotUtils.getIOSMobileScreenShot(serial);
File file = deviceHandleHelper.getScreenShotFile(serial);
logger.debug("执行步骤token{}ocr截图{}", request.getStepToken(), file.getAbsolutePath());
return getFileBase64(file);
}
@Override
public void getVerificationCodeByNode(CmdAutomationRequest request) {
@ -624,8 +602,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) {
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
} else {
File shotFile = deviceHandleHelper.getScreenShotFile(serial, uiNodeData.getX(), uiNodeData.getY(), uiNodeData.getWidth(), uiNodeData.getHeight(), phoneEntity.getScreenWidth(), phoneEntity.getScreenHeight());
String targetBase64 = getFileBase64(shotFile);
int x = uiNodeData.getX() * phoneEntity.getScale();
int y = uiNodeData.getY() * phoneEntity.getScale();
int width = uiNodeData.getWidth() * phoneEntity.getScale();
int height = uiNodeData.getHeight() * phoneEntity.getScale();
File screenShotFile = deviceHandleHelper.getScreenShotFile(phoneEntity.getUdid(), x, y, width, height);
String targetBase64 = getFileBase64(screenShotFile);
if (StringUtils.isBlank(targetBase64)) {
response = CmdAutomationResponse.builderFailure(request, "未获取到ocr区域");
return;
@ -939,46 +921,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
private TapXYData queryPoint(CmdAutomationRequest request) {
TapXYData tapXYData = null;
Map<String, Object> data = request.getData();
Integer x = (Integer) data.get(UpperParamKey.X);
Integer y = (Integer) data.get(UpperParamKey.Y);
Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH);
Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT);
boolean overTurn = false;
if (screenWidth > screenHeight) {
overTurn = true;
}
int width = phoneEntity.getScreenWidth()* phoneEntity.getScale();
int height = phoneEntity.getScreenHeight()*phoneEntity.getScale();
int realityWidth = width;
int realityHeight = height;
if (overTurn) { //宽高反了的情况,调换
if (width < height) {
realityWidth = height;
realityHeight = width;
}
} else {
if (width > height) {
realityWidth = height;
realityHeight = width;
}
}
Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth;
Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight;
if (realityX <= 0 || realityX >= realityWidth || realityY <= 0 || realityY >= realityHeight) {
logger.warn("步骤id【{}】坐标方式超出了屏幕范围", request.getStepToken());
}
int clickTrueX = new Double(realityX / phoneEntity.getScale()).intValue();
int clickTrueY = new Double(realityY / phoneEntity.getScale()).intValue();
tapXYData = new TapXYData();
tapXYData.setX(clickTrueX);
tapXYData.setY(clickTrueY);
logger.debug("实际点击的位置------->>>>>>x:{},y:{}", clickTrueX, clickTrueY);
return tapXYData;
}
@Override
public void handleNodeByOcr(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr点击控件失败").withData(false);
@ -1425,10 +1367,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
return allPoint.get(index - 1);
}
private String getFileBase64(File file) {
String encode = Base64.encode(file);
return encode;
}
@Override
public void handleNodeByImage(CmdAutomationRequest request) {
@ -1722,6 +1660,14 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
@Override
public void initScreenData() {
this.screenInfo = new ScreenInfo();
this.screenInfo.setWidth(this.phoneEntity.getScreenWidth() * this.phoneEntity.getScale());
this.screenInfo.setHeight(this.phoneEntity.getScreenHeight() * this.phoneEntity.getScale());
this.screenInfo.setScale(this.phoneEntity.getScale());
}
@Override
public void pressHomeKey(CmdAutomationRequest request) {
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "点击home键失败").withData(false);
@ -1889,42 +1835,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
/**
*
* @param originFile
* @param x
* @param y
* @return
*/
private File addSignatureToImage(File originFile, Integer x, Integer y) {
if (x == null || y == null) {
return originFile;
}
if (!originFile.exists()) {
logger.info("截图文件不存在:{}", originFile.getAbsolutePath());
return originFile;
}
String fullName = originFile.getName();
String suffix = fullName.substring(fullName.lastIndexOf(".") + 1);
String name = fullName.substring(0, fullName.lastIndexOf("."));
File targetFile = new File(originFile.getParentFile().getAbsolutePath() + File.separator + name + "_with_dot" + "." + suffix);
try {
BufferedImage bi = ImageIO.read(originFile);
Graphics2D g2d = bi.createGraphics(); // 生成画布
g2d.setColor(Color.RED); // 红色
g2d.fillOval(x, y, 20, 20); // 在图片的xy上画上一个直径20的实心原点
g2d.dispose();
ImageIO.write(bi, suffix, targetFile);
} catch (IOException e) {
logger.error("io异常", e);
} catch (Exception e) {
logger.error("添加红点失败", e);
}
if (!targetFile.exists()) {
targetFile = originFile;
}
return targetFile;
}
@Override
public void getElementValueByPathOcr(CmdAutomationRequest request) {
@ -2339,14 +2250,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
private String getOcrAreaByBodeToBase64(UiNodeData uiNodeData) {
Integer x = uiNodeData.getX();
Integer y = uiNodeData.getY();
Integer screenWidth = phoneEntity.getScreenWidth();
Integer screenHeight = phoneEntity.getScreenHeight();
Integer cutWidth = uiNodeData.getWidth();
Integer cutHeight = uiNodeData.getHeight();
logger.debug("拿到的截图参数----->>>>>x:{},y:{},screenWidth:{},screenHeight:{},cutWidth:{},cutHeight:{}", x, y, screenWidth, screenHeight, cutWidth, cutHeight);
File file = deviceHandleHelper.getScreenShotFile(serial, x, y, cutWidth, cutHeight, screenWidth, screenHeight);
int x = uiNodeData.getX() * phoneEntity.getScale();
int y = uiNodeData.getY() * phoneEntity.getScale();
int width = uiNodeData.getWidth() * phoneEntity.getScale();
int height = uiNodeData.getHeight() * phoneEntity.getScale();
logger.debug("拿到的截图参数----->>>>>x:{},y:{},cutWidth:{},cutHeight:{}", x, y, width, height);
File file = deviceHandleHelper.getScreenShotFile(phoneEntity.getUdid(), x, y, width, height);
logger.debug("ocr截图{}", file.getAbsolutePath());
String base64Str = getFileBase64(file);
return base64Str;
@ -2604,75 +2513,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
}
}
private TapXYData queryImageArea(CmdAutomationRequest request) {
TapXYData result = null;
Map<String, Object> data = request.getData();
String picName = (String) data.get(UpperParamKey.IMG_URL);
String resolution_s = (String) data.get(UpperParamKey.RESOLUTION_S);
String resolution_l = (phoneEntity.getScreenWidth() * phoneEntity.getScale()) + "x"
+ (phoneEntity.getScreenHeight() * phoneEntity.getScale());
logger.info("以图找图分辨率参数resolution_s:{}, resolution_l:{}", resolution_s, resolution_l);
String serverAddress = SpringUtils.getProperties("nk.mobile-computer.serverAddr"); //图片下载地址
String picDownloadAddress = SpringUtils.getProperties("nk.mobile-computer.publicDownloadFile"); //图片下载地址
logger.info("图片下载地址{}", picDownloadAddress);
String picDownloadUrl = serverAddress + picDownloadAddress.replace("{appUrl}", picName);
HttpUtils.downloadFileToLocal(picDownloadUrl, System.getProperty("user.dir") + "/tempPic", picName);
File file = new File(System.getProperty("user.dir") + "/tempPic/" + picName);
String targetBase64 = Base64.encode(file);
File sourceFile = deviceHandleHelper.getScreenShotFile(serial); // 通过helper进行截图。
String sourceBase64 = Base64.encode(sourceFile);
if (StringUtils.isNotBlank(targetBase64) && StringUtils.isNotBlank(sourceBase64)) {
Map<String, BigDecimal> resultPoint = null;
String url = SpringUtils.getProperties("nk.http-request-path.imgFindArea");
String matchingThresholdString = SpringUtils.getProperties("nk.http-request-path.matchingThreshold");
double matchingThreshold = matchingThresholdString != null ? Double.parseDouble(matchingThresholdString) : 0.5;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> param = new HashMap<>();
param.put("sourceImage", sourceBase64);
param.put("targetImage", targetBase64);
param.put("matchingThreshold", matchingThreshold);
param.put("resolution_s", StringUtils.isNotBlank(resolution_s) ? resolution_s.replace("*", "x") : "");
param.put("resolution_l", resolution_l);
logger.debug("请求图像解析服务参数:{}", JSON.toJSONString(param));
HttpEntity<Map> httpEntity = new HttpEntity<>(param, headers);
try {
URI uri = new URI(url);
Map body = HttpUtils.doPost(uri, httpEntity, Map.class);
Map<String, Object> bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap();
logger.debug("以图找图返回结果:{}", JSON.toJSONString(body));
if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) {
resultPoint = (Map<String, BigDecimal>) bodyMap.get("data");
}
} catch (URISyntaxException e) {
logger.error("找图像地址有误,:{}", e);
} catch (Exception e) {
logger.error("请求图像解析服务失败,参数:{},错误:{}", JSON.toJSONString(param), e);
}
if (null != resultPoint) {
logger.info("步骤【{}】找到区域位置为:{}", request.getStepToken(), JSON.toJSONString(resultPoint));
try {
byte[] bytes = new BASE64Decoder().decodeBuffer(targetBase64);
BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes));
Integer width = read.getWidth();
Integer height = read.getHeight();
int clickTrueX = new Double((resultPoint.get("x").doubleValue() + resultPoint.get("w").doubleValue() / 2) / phoneEntity.getScale()).intValue();
int clickTrueY = new Double((resultPoint.get("y").doubleValue() + resultPoint.get("h").doubleValue() / 2) / phoneEntity.getScale()).intValue();
result = new TapXYData();
result.setX(clickTrueX);
result.setY(clickTrueY);
} catch (IOException e) {
logger.error("转换base64图片失败{}", e);
}
} else {
logger.warn("步骤【{}】未找到对应的图像", request.getStepToken());
}
} else {
logger.warn("步骤【{}】下载的图像或者截图为空...............", request.getStepToken());
}
return result;
}
private NKAgent getNkAgent() {
int times = 0;
NKAgent nkAgent = null;
@ -2705,45 +2545,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
return nkAgent;
}
private Map<String, Object> getAppDetail(String appId) throws URISyntaxException {
Map<String, Object> data = null;
String path = SpringUtils.getProperties("nk.mobile-computer.publicFindAppInfo");
String server = SpringUtils.getProperties("nk.mobile-computer.serverAddr");
JSONObject result = HttpUtils.doGet(new URI(server + path + "?appId=" + appId), JSONObject.class);
logger.debug("查询app的结果{}", JSONObject.toJSONString(result));
if (result.getBoolean("success")) {
data = result.getJSONObject("data");
}
return data;
}
/**
* app
*
* @param appId
* @param appName
*/
private boolean installApp(String appId, String appName) {
String appPath = "";
logger.info("Begin to download app[{}] to upperComputer...", appName);
try {
appPath = deviceHandleHelper.downloadAppToLocal(appId, appName);
} catch (Exception e) {
logger.error("下载app[{}]出错app名字{}", appId, appName, e);
return false;
}
if (StringUtils.isBlank(appPath)) {
return false;
}
logger.info("download app[{}] finish,start install ............", appName);
boolean installSuccess = false;
try {
installSuccess = deviceHandleHelper.installApp(phoneEntity.getUdid(), appPath);
} catch (Exception e) {
logger.error("安装app[{}]出错app名字{}", appId, appName, e);
return false;
}
logger.info("App[{}] on device[{}] installed finish,success:{}", appName, phoneEntity.getUdid(), installSuccess);
return installSuccess;
}
}

View File

@ -0,0 +1,241 @@
package net.northking.cctp.upperComputer.automation.utils;
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import net.northking.cctp.upperComputer.config.HttpRequestPathConfig;
import net.northking.cctp.upperComputer.driver.ios.command.data.TapXYData;
import net.northking.cctp.upperComputer.exception.ExecuteException;
import net.northking.cctp.upperComputer.utils.HttpUtils;
import net.northking.cctp.upperComputer.utils.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
/**
* @author : yineng.huang
* @date : 2024/11/5 16:38
*/
public class CloudTestOcrHelper implements OcrHelper {
private Logger logger = LoggerFactory.getLogger(CloudTestOcrHelper.class);
private String ocrGetAllTextNum;
private String ocrImgFindArea;
private double matchingThreshold = 0.5;
private String ocrGetVerificationCode;
private String ocrGetSafeKeyBoardNum;
private String ocrGetTextDirectionText;
public CloudTestOcrHelper() {
HttpRequestPathConfig config = SpringUtils.getBean(HttpRequestPathConfig.class);
ocrGetAllTextNum = config.getOcrGetAllTextNum();
ocrImgFindArea = config.getImgFindArea();
matchingThreshold = config.getMatchingThreshold() == null ? 0.5 : Double.parseDouble(config.getMatchingThreshold());
ocrGetVerificationCode = config.getOcrGetVerificationCode();
ocrGetSafeKeyBoardNum = config.getGetSafeKeyBoardNum();
ocrGetTextDirectionText = config.getOcrGetTextDirectionText();
}
@Override
public TapXYData doFindTextByOcr(String base64Str, String ocrText, Integer diffussion, Integer index, Double x, Double y) {
TapXYData result = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("img_base64", base64Str);
ocrParamMap.put("targets", ocrText);
ocrParamMap.put("diffussion", diffussion != null ? diffussion : 1);
HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
List ocrResultList = null;
try {
ocrResultList = HttpUtils.doPost(ocrGetAllTextNum, ocrEntity, List.class);
logger.info("得到的ocr结果{}", ocrResultList);
} catch (Exception e) {
logger.error("ocr失败", e);
}
if (!CollectionUtils.isEmpty(ocrResultList)) {
Object all = ocrResultList.get(0);
JSONObject jsonObject = JSONObject.parseObject(JSONArray.toJSONString(all));
//todo如果传了;是多个文本一起找,暂时只考虑一个文本
JSONArray pointData = jsonObject.getJSONArray(ocrText); //所有坐标
JSONObject pointMap = null;
if (pointData != null && pointData.size() == 1) { //只找到一个,直接返回
pointMap = pointData.getJSONObject(0);
logger.info("只找到一个,直接返回结果:{}", pointMap);
result = new TapXYData();
result.setX((x.intValue() + pointMap.getInteger("x") + pointMap.getInteger("w") / 2));
result.setY((y.intValue() + pointMap.getInteger("y") + pointMap.getInteger("h") / 2));
} else if (pointData != null && pointData.size() > 1) { //找到多个,选择用户选择的第几个
if (index > 0) {
if (index <= pointData.size()) {
JSONObject dataJSONObject = pointData.getJSONObject(index - 1);
result = new TapXYData();
result.setX((dataJSONObject.getInteger("x") + dataJSONObject.getInteger("w") / 2));
result.setY((dataJSONObject.getInteger("y") + dataJSONObject.getInteger("h") / 2));
} else {
logger.warn("实际找到{}个,选择第{}个", pointData.size(), index);
throw new ExecuteException(String.format("选择元素的位置大于找到元素的个数,找到元素%s个", pointData.size()));
}
}
}
}
return result;
}
@Override
public TapXYData doImgFindImg(File sourceFile, String resolution_s, String resolution_l, String picName, String stepToken) {
TapXYData result = null;
logger.info("以图找图分辨率参数resolution_s:{}, resolution_l:{}", resolution_s, resolution_l);
String serverAddress = SpringUtils.getProperties("nk.mobile-computer.serverAddr"); //图片下载地址
String picDownloadAddress = SpringUtils.getProperties("nk.mobile-computer.publicDownloadFile"); //图片下载地址
logger.info("图片下载地址{}", picDownloadAddress);
String picDownloadUrl = serverAddress + picDownloadAddress.replace("{appUrl}", picName);
HttpUtils.downloadFileToLocal(picDownloadUrl, System.getProperty("user.dir") + "/tempPic", picName);
File file = new File(System.getProperty("user.dir") + "/tempPic/" + picName);
String targetBase64 = Base64.encode(file);
String sourceBase64 = Base64.encode(sourceFile);
if (StringUtils.isNotBlank(targetBase64) && StringUtils.isNotBlank(sourceBase64)) {
Map<String, BigDecimal> resultPoint = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> param = new HashMap<>();
param.put("sourceImage", sourceBase64);
param.put("targetImage", targetBase64);
param.put("matchingThreshold", matchingThreshold);
param.put("resolution_s", StringUtils.isNotBlank(resolution_s) ? resolution_s.replace("*", "x") : "");
param.put("resolution_l", resolution_l);
HttpEntity<Map> httpEntity = new HttpEntity<>(param, headers);
try {
URI uri = new URI(ocrImgFindArea);
Map body = HttpUtils.doPost(uri, httpEntity, Map.class);
Map<String, Object> bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap();
if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) {
resultPoint = (Map<String, BigDecimal>) bodyMap.get("data");
}
} catch (URISyntaxException e) {
logger.error("找图像地址有误,:{}", e);
} catch (Exception e) {
logger.error("请求图像解析服务失败,参数:{},错误:{}", JSON.toJSONString(param), e);
}
if (null != resultPoint) {
logger.info("步骤【{}】找到区域位置为:{}", stepToken, JSON.toJSONString(resultPoint));
int clickTrueX = new Double((resultPoint.get("x").doubleValue() + resultPoint.get("w").doubleValue() / 2)).intValue();
int clickTrueY = new Double((resultPoint.get("y").doubleValue() + resultPoint.get("h").doubleValue() / 2)).intValue();
result = new TapXYData();
result.setX(clickTrueX);
result.setY(clickTrueY);
} else {
logger.warn("步骤【{}】未找到对应的图像", stepToken);
}
} else {
logger.warn("步骤【{}】下载的图像或者截图为空...............", stepToken);
}
return result;
}
@Override
public String getTextValue(String targetBase64){
String value = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("img_base64", targetBase64);
ocrParamMap.put("targets", "");
HttpEntity<Map> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
List ocrResultList = null;
try {
logger.info("传参:{}", JSON.toJSONString(ocrParamMap));
ocrResultList = HttpUtils.doPost(ocrGetAllTextNum, ocrEntity, List.class);
logger.info("得到的ocr结果{}", ocrResultList);
} catch (Exception e) {
logger.error("ocr失败", e);
}
StringJoiner dataList = new StringJoiner("");
if (!CollectionUtils.isEmpty(ocrResultList)) {
if (!CollectionUtils.isEmpty(ocrResultList)) {
for (Object o : ocrResultList) {
dataList.add(((Map) o).get("text") + "");
}
}
value = dataList.toString();
}
logger.info("识别的图片文字为:{}", value);
return value;
}
@Override
public String getVerificationCodeByBase64(String base64, Integer codeType) {
String value = null;
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("img_base64", base64);
ocrParamMap.put("type", codeType);
HttpEntity<Map<String, Object>> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
value = HttpUtils.doPost(new URI(ocrGetVerificationCode), ocrEntity, String.class);
}catch (URISyntaxException e) {
logger.error("ocr地址有误===>{}",ocrGetVerificationCode,e);
} catch (Exception e) {
logger.error("请求ocr接口失败接口地址{}",ocrGetVerificationCode,e);
}
logger.info("识别到的验证码为:{}", value);
return value == null ? value : value.replace("\"", "");
}
public List getKeyBoardInfo(String ocrArea) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("img_base64", ocrArea);
HttpEntity<Map> entity = new HttpEntity<>(paramMap, httpHeaders);
logger.info("识别键盘地址为:{}", ocrGetSafeKeyBoardNum);
List result = HttpUtils.doPost(ocrGetSafeKeyBoardNum, entity, List.class);
return result;
}
public JSONObject getTextDirectionText(String imgBase64,String text,String direction,Integer index){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> ocrParamMap = new HashMap<>();
ocrParamMap.put("image_base64", imgBase64);
ocrParamMap.put("targets", text);
ocrParamMap.put("direction", direction);
ocrParamMap.put("n", index);
HttpEntity<Map> ocrEntity = new HttpEntity<>(ocrParamMap, headers);
JSONObject ocrResultMap = null;
try {
logger.info("传参:{}", JSON.toJSONString(ocrParamMap));
ocrResultMap = HttpUtils.doPostForGetTextDirectionText(ocrGetTextDirectionText, ocrEntity, JSONObject.class);
logger.info("得到的ocr结果{}", ocrResultMap);
} catch (Exception e) {
logger.error("ocr失败", e);
if (e != null && e.getMessage() != null && e.getMessage().contains("Target text not found in image")) {
throw new ExecuteException("未找到指定文本");
}
}
return ocrResultMap;
}
}

View File

@ -0,0 +1,71 @@
package net.northking.cctp.upperComputer.automation.utils;
import com.alibaba.fastjson.JSONObject;
import net.northking.cctp.upperComputer.driver.ios.command.data.TapXYData;
import java.io.File;
import java.util.List;
/**
* @author : yineng.huang
* @date : 2024/11/5 16:33
*/
public interface OcrHelper {
/**
* ocr
* @param base64Str
* @param ocrText
* @param diffussion
* @param index
* @param x x
* @param y y
* @return
*/
public TapXYData doFindTextByOcr(String base64Str,String ocrText,Integer diffussion,Integer index,Double x,Double y);
/**
*
* @param sourceFile
* @param resolution_s
* @param resolution_l
* @param picName
* @param stepToken id
* @return
*/
public TapXYData doImgFindImg(File sourceFile, String resolution_s, String resolution_l, String picName, String stepToken);
/**
*
* @param targetBase64 base64
* @return
*/
public String getTextValue(String targetBase64);
/**
*
* @param base64 base64
* @param codeType
* @return
*/
public String getVerificationCodeByBase64(String base64, Integer codeType);
/**
*
* @param ocrArea
* @return
*/
public List getKeyBoardInfo(String ocrArea);
/**
*
* @param imgBase64
* @param text
* @param direction
* @param index
* @return
*/
public JSONObject getTextDirectionText(String imgBase64, String text, String direction, Integer index);
}

View File

@ -25,6 +25,38 @@ public class HttpRequestPathConfig {
private String ocrGetAllTextNum;
private String matchingThreshold; //图像匹配阈值
private String ocrGetTableColValue; //ocr获取表格列内容
private String ocrGetTextDirectionText; //ocr识别指定文字指定方向的内容
public String getOcrGetTextDirectionText() {
return ocrGetTextDirectionText;
}
public void setOcrGetTextDirectionText(String ocrGetTextDirectionText) {
this.ocrGetTextDirectionText = ocrGetTextDirectionText;
}
public String getMatchingThreshold() {
return matchingThreshold;
}
public void setMatchingThreshold(String matchingThreshold) {
this.matchingThreshold = matchingThreshold;
}
public String getOcrGetTableColValue() {
return ocrGetTableColValue;
}
public void setOcrGetTableColValue(String ocrGetTableColValue) {
this.ocrGetTableColValue = ocrGetTableColValue;
}
public String getOcrGetAllTextNum() {
return ocrGetAllTextNum;
}

View File

@ -0,0 +1,171 @@
package net.northking.cctp.upperComputer.constants;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HarmonyKeyCode;
public enum HarmonyKeyBoardCodeEnum {
ESCAPE("Escape", HarmonyKeyCode.KEYCODE_ESCAPE),
F1("F1",HarmonyKeyCode.KEYCODE_F1),
F2("F2",HarmonyKeyCode.KEYCODE_F2),
F3("F3",HarmonyKeyCode.KEYCODE_F3),
F4("F4",HarmonyKeyCode.KEYCODE_F4),
F5("F5",HarmonyKeyCode.KEYCODE_F5),
F6("F6",HarmonyKeyCode.KEYCODE_F6),
F7("F7",HarmonyKeyCode.KEYCODE_F7),
F8("F8",HarmonyKeyCode.KEYCODE_F8),
F9("F9",HarmonyKeyCode.KEYCODE_F9),
F10("F10",HarmonyKeyCode.KEYCODE_F10),
F11("F11",HarmonyKeyCode.KEYCODE_F11),
F12("F12",HarmonyKeyCode.KEYCODE_F12),
KEYCODE_GRAVE("`",HarmonyKeyCode.KEYCODE_GRAVE),
NUMBER0("0",HarmonyKeyCode.KEYCODE_0),
NUMBER1("1",HarmonyKeyCode.KEYCODE_1),
NUMBER2("2",HarmonyKeyCode.KEYCODE_2),
NUMBER3("3",HarmonyKeyCode.KEYCODE_3),
NUMBER4("4",HarmonyKeyCode.KEYCODE_4),
NUMBER5("5",HarmonyKeyCode.KEYCODE_5),
NUMBER6("6",HarmonyKeyCode.KEYCODE_6),
NUMBER7("7",HarmonyKeyCode.KEYCODE_7),
NUMBER8("8",HarmonyKeyCode.KEYCODE_8),
NUMBER9("9",HarmonyKeyCode.KEYCODE_9),
KEYCODE_MINUS("-",HarmonyKeyCode.KEYCODE_MINUS),
KEYCODE_EQUALS("=",HarmonyKeyCode.KEYCODE_EQUALS),
KEYCODE_GRAVE_UP("~",HarmonyKeyCode.KEYCODE_GRAVE),
NUMBER0_UP(")",HarmonyKeyCode.KEYCODE_0),
NUMBER1_UP("!",HarmonyKeyCode.KEYCODE_1),
NUMBER2_UP("@",HarmonyKeyCode.KEYCODE_2),
NUMBER3_UP("#",HarmonyKeyCode.KEYCODE_3),
NUMBER4_UP("$",HarmonyKeyCode.KEYCODE_4),
NUMBER5_UP("%",HarmonyKeyCode.KEYCODE_5),
NUMBER6_UP("^",HarmonyKeyCode.KEYCODE_6),
NUMBER7_UP("&",HarmonyKeyCode.KEYCODE_7),
NUMBER8_UP("*",HarmonyKeyCode.KEYCODE_8),
KEYCODE_NUMPAD_MULTIPLY("_*",HarmonyKeyCode.KEYCODE_NUMPAD_MULTIPLY),
NUMBER9_UP("(",HarmonyKeyCode.KEYCODE_9),
KEYCODE_MINUS_UP("_",HarmonyKeyCode.KEYCODE_MINUS),
KEYCODE_NUMPAD_SUBTRACT("__",HarmonyKeyCode.KEYCODE_NUMPAD_SUBTRACT),
KEYCODE_EQUALS_UP("+",HarmonyKeyCode.KEYCODE_EQUALS),
KEYCODE_NUMPAD_ADD("_+",HarmonyKeyCode.KEYCODE_NUMPAD_ADD),
KEYCODE_ENTER("Enter",HarmonyKeyCode.KEYCODE_ENTER),
KEYCODE_DEL("Backspace",HarmonyKeyCode.KEYCODE_DEL),
KEYCODE_TAB("Tab",HarmonyKeyCode.KEYCODE_TAB),
KEYCODE_CAPS_LOCK("CapsLock",HarmonyKeyCode.KEYCODE_CAPS_LOCK),
KEYCODE_SHIFT_LEFT("Shift",HarmonyKeyCode.KEYCODE_SHIFT_LEFT),
KEYCODE_CTRL_LEFT("Control",HarmonyKeyCode.KEYCODE_CTRL_LEFT),
KEYCODE_META_LEFT("Meta",HarmonyKeyCode.KEYCODE_META_LEFT),
KEYCODE_ALT_LEFT("Alt",HarmonyKeyCode.KEYCODE_ALT_LEFT),
KEYCODE_SPACE(" ",HarmonyKeyCode.KEYCODE_SPACE),
FN("Fn",HarmonyKeyCode.KEYCODE_SPACE),
KEYCODE_SLASH("/",HarmonyKeyCode.KEYCODE_SLASH),
KEYCODE_BACKSLASH("\\",HarmonyKeyCode.KEYCODE_BACKSLASH),
KEYCODE_COMMA(",",HarmonyKeyCode.KEYCODE_COMMA),
KEYCODE_PERIOD(".",HarmonyKeyCode.KEYCODE_PERIOD),
KEYCODE_SEMICOLON(";",HarmonyKeyCode.KEYCODE_SEMICOLON),
KEYCODE_APOSTROPHE("'",HarmonyKeyCode.KEYCODE_APOSTROPHE),
KEYCODE_LEFT_BRACKET("[",HarmonyKeyCode.KEYCODE_LEFT_BRACKET),
KEYCODE_RIGHT_BRACKET("]",HarmonyKeyCode.KEYCODE_RIGHT_BRACKET),
KEYCODE_SLASH_UP("?",HarmonyKeyCode.KEYCODE_SLASH),
KEYCODE_BACKSLASH_UP("|",HarmonyKeyCode.KEYCODE_BACKSLASH),
KEYCODE_COMMA_UP("<",HarmonyKeyCode.KEYCODE_COMMA),
KEYCODE_PERIOD_UP(">",HarmonyKeyCode.KEYCODE_PERIOD),
KEYCODE_SEMICOLON_UP(":",HarmonyKeyCode.KEYCODE_SEMICOLON),
KEYCODE_APOSTROPHE_UP("\"",HarmonyKeyCode.KEYCODE_APOSTROPHE),
KEYCODE_LEFT_BRACKET_UP("{",HarmonyKeyCode.KEYCODE_LEFT_BRACKET),
KEYCODE_RIGHT_BRACKET_UP("}",HarmonyKeyCode.KEYCODE_RIGHT_BRACKET),
KEYCODE_FORWARD_DEL("Delete",HarmonyKeyCode.KEYCODE_FORWARD_DEL),
KEYCODE_MOVE_END("End",HarmonyKeyCode.KEYCODE_MOVE_END),
KEYCODE_PAGE_DOWN("PageDown",HarmonyKeyCode.KEYCODE_PAGE_DOWN),
KEYCODE_PAGE_UP("PageUp",HarmonyKeyCode.KEYCODE_PAGE_UP),
KEYCODE_INSERT("Insert",HarmonyKeyCode.KEYCODE_INSERT),
KEYCODE_MOVE_HOME("Home",HarmonyKeyCode.KEYCODE_MOVE_HOME),
KEYCODE_SYSRQ("PrintScreen",HarmonyKeyCode.KEYCODE_SYSRQ),
KEYCODE_SCROLL_LOCK("ScrollLock",HarmonyKeyCode.KEYCODE_SCROLL_LOCK),
KEYCODE_BREAK("Pause",HarmonyKeyCode.KEYCODE_BREAK),
KEYCODE_A("a",HarmonyKeyCode.KEYCODE_A),
KEYCODE_B("b",HarmonyKeyCode.KEYCODE_B),
KEYCODE_C("c",HarmonyKeyCode.KEYCODE_C),
KEYCODE_D("d",HarmonyKeyCode.KEYCODE_D),
KEYCODE_E("e",HarmonyKeyCode.KEYCODE_E),
KEYCODE_F("f",HarmonyKeyCode.KEYCODE_F),
KEYCODE_G("g",HarmonyKeyCode.KEYCODE_G),
KEYCODE_H("h",HarmonyKeyCode.KEYCODE_H),
KEYCODE_I("i",HarmonyKeyCode.KEYCODE_I),
KEYCODE_J("j",HarmonyKeyCode.KEYCODE_J),
KEYCODE_K("k",HarmonyKeyCode.KEYCODE_K),
KEYCODE_L("l",HarmonyKeyCode.KEYCODE_L),
KEYCODE_M("m",HarmonyKeyCode.KEYCODE_M),
KEYCODE_N("n",HarmonyKeyCode.KEYCODE_N),
KEYCODE_O("o",HarmonyKeyCode.KEYCODE_O),
KEYCODE_P("p",HarmonyKeyCode.KEYCODE_P),
KEYCODE_Q("q",HarmonyKeyCode.KEYCODE_Q),
KEYCODE_R("r",HarmonyKeyCode.KEYCODE_R),
KEYCODE_S("s",HarmonyKeyCode.KEYCODE_S),
KEYCODE_T("t",HarmonyKeyCode.KEYCODE_T),
KEYCODE_U("u",HarmonyKeyCode.KEYCODE_U),
KEYCODE_V("v",HarmonyKeyCode.KEYCODE_V),
KEYCODE_W("w",HarmonyKeyCode.KEYCODE_W),
KEYCODE_X("x",HarmonyKeyCode.KEYCODE_X),
KEYCODE_Y("y",HarmonyKeyCode.KEYCODE_Y),
KEYCODE_Z("z",HarmonyKeyCode.KEYCODE_Z),
KEYCODE_A_LARGE("A",HarmonyKeyCode.KEYCODE_A),
KEYCODE_B_LARGE("B",HarmonyKeyCode.KEYCODE_B),
KEYCODE_C_LARGE("C",HarmonyKeyCode.KEYCODE_C),
KEYCODE_D_LARGE("D",HarmonyKeyCode.KEYCODE_D),
KEYCODE_E_LARGE("E",HarmonyKeyCode.KEYCODE_E),
KEYCODE_F_LARGE("F",HarmonyKeyCode.KEYCODE_F),
KEYCODE_G_LARGE("G",HarmonyKeyCode.KEYCODE_G),
KEYCODE_H_LARGE("H",HarmonyKeyCode.KEYCODE_H),
KEYCODE_I_LARGE("I",HarmonyKeyCode.KEYCODE_I),
KEYCODE_J_LARGE("J",HarmonyKeyCode.KEYCODE_J),
KEYCODE_K_LARGE("K",HarmonyKeyCode.KEYCODE_K),
KEYCODE_L_LARGE("L",HarmonyKeyCode.KEYCODE_L),
KEYCODE_M_LARGE("M",HarmonyKeyCode.KEYCODE_M),
KEYCODE_N_LARGE("N",HarmonyKeyCode.KEYCODE_N),
KEYCODE_O_LARGE("O",HarmonyKeyCode.KEYCODE_O),
KEYCODE_P_LARGE("O",HarmonyKeyCode.KEYCODE_P),
KEYCODE_Q_LARGE("Q",HarmonyKeyCode.KEYCODE_Q),
KEYCODE_R_LARGE("R",HarmonyKeyCode.KEYCODE_R),
KEYCODE_S_LARGE("S",HarmonyKeyCode.KEYCODE_S),
KEYCODE_T_LARGE("T",HarmonyKeyCode.KEYCODE_T),
KEYCODE_U_LARGE("U",HarmonyKeyCode.KEYCODE_U),
KEYCODE_V_LARGE("V",HarmonyKeyCode.KEYCODE_V),
KEYCODE_W_LARGE("W",HarmonyKeyCode.KEYCODE_W),
KEYCODE_X_LARGE("X",HarmonyKeyCode.KEYCODE_X),
KEYCODE_Y_LARGE("Y",HarmonyKeyCode.KEYCODE_Y),
KEYCODE_Z_LARGE("Z",HarmonyKeyCode.KEYCODE_Z);
public String getWebValue() {
return webValue;
}
private String webValue;
private int code;
HarmonyKeyBoardCodeEnum(String webValue, int code){
this.webValue = webValue;
this.code = code;
}
public static int getCode(String webValue,boolean numberBoard){
if (numberBoard) {
if ("*".equals(webValue)) {
return KEYCODE_NUMPAD_MULTIPLY.code;
} else if ("+".equals(webValue)) {
return KEYCODE_NUMPAD_ADD.code;
} else if ("-".equals(webValue)) {
return KEYCODE_NUMPAD_SUBTRACT.code;
}
}
for (HarmonyKeyBoardCodeEnum value : HarmonyKeyBoardCodeEnum.values()) {
if (value.webValue.equals(webValue)) {
return value.code;
}
}
return -1;
}
public int getCode() {
return code;
}
}

View File

@ -1,6 +1,7 @@
package net.northking.cctp.upperComputer.deviceManager;
import com.alibaba.fastjson.JSON;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import net.northking.cctp.upperComputer.entity.CdDeviceModel;
import net.northking.cctp.upperComputer.entity.CdDeviceRegisterDto;
import net.northking.cctp.upperComputer.entity.CdMobileBrand;
@ -23,6 +24,7 @@ import java.net.URI;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author : yineng.huang
@ -32,6 +34,8 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana
private Logger logger = LoggerFactory.getLogger(AbstractDeviceManager.class);
private ConcurrentHashMap<String, ScreenInfo> screenInfoMap = new ConcurrentHashMap<>();
@Override
public void publishDeviceAgentAlready(String serial) {
SpringUtils.getBean(DeviceConnectionService.class).deviceAgentAlready(serial);
@ -190,4 +194,9 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana
public void offlineDevice(String serial) {
}
@Override
public void setScreenInfo(String deviceId, ScreenInfo screenInfo) {
this.screenInfoMap.put(deviceId, screenInfo);
}
}

View File

@ -211,6 +211,7 @@ public class AndroidDeviceManager extends AbstractDeviceManager {
}
@Override
public boolean checkMobileIsOnline(String serial) {
AndroidDeviceInitThread initThread = onlineDeviceInitMap.get(serial);
if (null != initThread && !initThread.isInterrupted() && initThread.isAlive()) {

View File

@ -1,5 +1,7 @@
package net.northking.cctp.upperComputer.deviceManager;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import java.util.LinkedHashMap;
import java.util.List;
@ -54,4 +56,9 @@ public interface DeviceManager {
* @param serial
*/
void offlineDevice(String serial);
public boolean checkMobileIsOnline(String serial);
public void setScreenInfo(String deviceId, ScreenInfo screenInfo);
}

View File

@ -0,0 +1,321 @@
package net.northking.cctp.upperComputer.deviceManager;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
import net.northking.cctp.upperComputer.deviceManager.listener.HarmonyDeviceListener;
import net.northking.cctp.upperComputer.deviceManager.screen.HarmonyScreenResponseThread;
import net.northking.cctp.upperComputer.deviceManager.thread.HarmonyProvider;
import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCConnectStatus;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.Hdc;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.AgentABI;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.Session;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 鸿
* <br>
* 鸿hdcAPI
*/
public class HarmonyDeviceManager extends AbstractDeviceManager {
private static final Logger log = LoggerFactory.getLogger(HarmonyDeviceManager.class);
private HarmonyDeviceListener harmonyDeviceListener = new HarmonyDeviceListener();
private ConcurrentHashMap<String, HarmonyScreenResponseThread> screenMap = new ConcurrentHashMap<>();
private static HarmonyDeviceManager instance;
public static HarmonyDeviceManager getInstance() {
if (instance == null) {
instance = new HarmonyDeviceManager();
}
return instance;
}
public static final Gson gson = new Gson();
/**
*
*/
private static String WORK_TMP_DIR;
/**
* CPUAgentMap
*/
private static final ConcurrentHashMap<AgentABI, File> AGENT_ABI_MAP = new ConcurrentHashMap<>();
/**
*
*/
private final ArrayList<HarmonyDevice> deviceList = new ArrayList<>();
/**
* Harmony线
*/
private ConcurrentHashMap<String, HarmonyProvider> onlineDeviceInitMap = new ConcurrentHashMap<>();
private HarmonyDeviceManager() {
WORK_TMP_DIR = UpperComputerManager.getInstance().getApplicationPath();
log.debug("临时目录:{}",UpperComputerManager.getInstance().getApplicationPath());
if (!setWorkTmpDir(WORK_TMP_DIR)) {
throw new RuntimeException("无法使用当前指定的临时工作目录请先指定可用目录再使用HarmonyDeviceManager");
}
if (!Hdc.getInstance().isObserveDeviceRunning()) {
Hdc.getInstance().startObserveDevice();
}
Hdc.getInstance().addHDCDeviceListener(harmonyDeviceListener);
registerAgentFile(AgentABI.ARM64, WORK_TMP_DIR + "/uitest_agent_v1.1.0.so");
}
/**
*
* <br>
* 使HarmonyDeviceManager
*
* @param path
* @return
*/
public static boolean setWorkTmpDir(String path) {
if (path == null || path.isEmpty()) {
log.error("设置的临时工作目录参数为空");
return false;
}
File dirFile = new File(path);
if (!dirFile.exists()) {
if (!dirFile.mkdirs()) {
log.error("创建临时工作目录失败: {}", path);
return false;
}
}
if (!dirFile.isDirectory()) {
log.error("设置的临时工作目录路径不是文件夹");
return false;
}
if (!dirFile.canRead()) {
log.error("设置的临时工作目录无法读取");
return false;
}
if (!dirFile.canWrite()) {
log.error("设置的临时工作目录无法写入");
return false;
}
WORK_TMP_DIR = dirFile.getAbsolutePath();
return true;
}
/**
*
*
* @param filename
* @return
*/
public static File getWorkTmpFile(String filename) {
return new File(WORK_TMP_DIR + "/" + filename);
}
/**
* agent
*
* @param abi
* @param path
* @return
*/
public static boolean registerAgentFile(AgentABI abi, String path) {
File file = new File(path);
if (!file.exists()) {
log.error("[registerAgentFile] Agent文件不存在: {}", path);
return false;
}
if (!file.isFile()) {
log.error("[registerAgentFile] Agent路径不是文件: {}", path);
return false;
}
if (!file.canRead()) {
log.error("[registerAgentFile] Agent文件不可读: {}", path);
return false;
}
AGENT_ABI_MAP.put(abi, file);
return true;
}
public static File getAgentFile(AgentABI abi) {
return AGENT_ABI_MAP.get(abi);
}
public ArrayList<HarmonyDevice> getDeviceList() {
return deviceList;
}
@Override
public void stopWebScreen(String udid, Session session) {
HarmonyScreenResponseThread screenThread = screenMap.remove(udid);
if (null != screenThread) {
screenThread.stopWebSessionTransform(session);
} else {
log.info("手机【{}】不存在推图的线程",udid);
}
}
@Override
public List<String> getOnlineDevice() {
Enumeration<String> keys = onlineDeviceInitMap.keys();
List<String> ids = new ArrayList<>();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
ids.add(key);
}
return ids;
}
@Override
public boolean checkMobileIsOnline(String serial) {
HarmonyProvider provider = onlineDeviceInitMap.get(serial);
if (null != provider) {
return true;
}
return false;
}
@Override
public void shutdown() {
Hdc.getInstance().stopObserveDevice(); //关闭hdc-server
synchronized (onlineDeviceInitMap) { //关闭harmony设备的agent
for (Map.Entry<String, HarmonyProvider> entry : onlineDeviceInitMap.entrySet()) {
HarmonyProvider provider = entry.getValue();
provider.exitDeviceInit();
}
onlineDeviceInitMap.clear();
}
//todo:设备和投屏
}
public void handleHarmonyDevice(ArrayList<HDCDevice> currentDevices) {
ArrayList<HarmonyDevice> ableDevice = new ArrayList<HarmonyDevice>();
ArrayList<HarmonyDevice> lostDevice = new ArrayList<HarmonyDevice>();
for (HDCDevice hdcDevice : currentDevices) {
boolean found = false;
for (HarmonyDevice device : deviceList) {
if (device.getHdcDevice().getConnectKey().equals(hdcDevice.getConnectKey())) {
found = true;
break;
}
}
if (!found) {
if (hdcDevice.getConnectStatus() == HDCConnectStatus.CONNECTED) {
HarmonyDevice device = new HarmonyDevice(hdcDevice);
device.enableTestMode();
ableDevice.add(device);
} else {
lostDevice.add(new HarmonyDevice(hdcDevice));
}
}
}
deviceList.addAll(ableDevice);
deviceList.addAll(lostDevice);
//更新状态
for (HarmonyDevice device : deviceList) {
boolean found = false;
for (HDCDevice hdcDevice : currentDevices) {
if (device.getHdcDevice().getConnectKey().equals(hdcDevice.getConnectKey())) {
found = true;
if (device.getHdcDevice().getConnectStatus() != hdcDevice.getConnectStatus()) {
if (device.getHdcDevice().getConnectStatus() == HDCConnectStatus.CONNECTED) {
lostDevice.add(device);
} else if (hdcDevice.getConnectStatus() == HDCConnectStatus.CONNECTED) {
device.enableTestMode();
ableDevice.add(device);
}
}
device.setHdcDevice(hdcDevice);
break;
}
}
if (!found) {
device.getHdcDevice().setConnectStatus(HDCConnectStatus.OFFLINE);
lostDevice.add(device);
}
}
for (HarmonyDevice device : ableDevice) {
onLineHarmonyDevice(device);
}
for (HarmonyDevice device : lostDevice) {
offlineDevice(device.getHdcDevice().getConnectKey());
}
}
private void onLineHarmonyDevice(HarmonyDevice device) {
HDCDevice hdcDevice = device.getHdcDevice();
log.info("新上来设备:{}", JSON.toJSONString(hdcDevice));
HarmonyProvider harmonyProvider = null;
try {
harmonyProvider = new HarmonyProvider(device);
} catch (IOException e) {
log.error("启动设备【{}】上的agent程序失败",hdcDevice.getConnectKey(),e);
return;
}
log.info("设备【{}】的初始化环境准备完成",hdcDevice.getConnectKey());
onlineDeviceInitMap.put(hdcDevice.getConnectKey(), harmonyProvider);
DisplaySize screenSize = harmonyProvider.getScreenSize();
int displayRotation = harmonyProvider.getDisplayRotation();
ScreenInfo screenInfo = new ScreenInfo();
screenInfo.setWidth(screenSize.width);
screenInfo.setHeight(screenSize.height);
screenInfo.setRotation(displayRotation);
setScreenInfo(hdcDevice.getConnectKey(),screenInfo);
publishDeviceOnlineToWebConnection(hdcDevice.getConnectKey());
}
@Override
public void offlineDevice(String serial) {
HarmonyProvider provider = onlineDeviceInitMap.remove(serial);
if (null != provider) {
provider.exitDeviceInit();
}
offlineDeviceFromDeviceManager(serial);
publishDeviceOfflineToWebConnection(serial);
}
public HarmonyDevice getCurrentDevice(String deviceId) {
for (HarmonyDevice harmonyDevice : deviceList) {
if (deviceId.equals(harmonyDevice.getHdcDevice().getConnectKey())) {
return harmonyDevice;
}
}
return null;
}
public HarmonyProvider getCurrentDeviceProvider(String serial){
return onlineDeviceInitMap.get(serial);
}
public HarmonyScreenResponseThread getScreenThread(String serial) {
return screenMap.get(serial);
}
public void saveScreenThread(String serial, HarmonyScreenResponseThread harmonyScreenResponseThread) {
screenMap.put(serial, harmonyScreenResponseThread);
}
public void removeScreenThread(String serial) {
screenMap.remove(serial);
log.info("移除手机【{}】的屏幕线程",serial);
}
}

View File

@ -288,6 +288,7 @@ public class IOSDeviceManager extends AbstractDeviceManager {
interrupt();
}
@Override
public boolean checkMobileIsOnline(String serial) {
PhoneEntity entity = this.phoneMap.get(serial);
if (null != entity && entity.getStatus()) {

View File

@ -70,8 +70,11 @@ public class UpperComputerManager {
List<String> onlineAndroidDeviceIds = AndroidDeviceManager.getInstance().getOnlineDevice();
//ios
List<String> onlineIosDeviceIds = IOSDeviceManager.getInstance().getOnlineDevice();
//harmony
List<String> onlineHarmonyDeviceIds = HarmonyDeviceManager.getInstance().getOnlineDevice();
onlineDeviceIds.addAll(onlineAndroidDeviceIds);
onlineDeviceIds.addAll(onlineIosDeviceIds);
onlineDeviceIds.addAll(onlineHarmonyDeviceIds);
this.computerHeartInfo.setDeviceIds(onlineDeviceIds);
return this.computerHeartInfo;
}

View File

@ -0,0 +1,23 @@
package net.northking.cctp.upperComputer.deviceManager.listener;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCDeviceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
/**
* @author : yineng.huang
* @date : 2024/10/15 16:04
*/
public class HarmonyDeviceListener implements HDCDeviceListener {
private final Logger logger = LoggerFactory.getLogger(HarmonyDeviceListener.class);
@Override
public void onDeviceChanged(ArrayList<HDCDevice> currentDevices) {
HarmonyDeviceManager.getInstance().handleHarmonyDevice(currentDevices);
}
}

View File

@ -0,0 +1,83 @@
package net.northking.cctp.upperComputer.deviceManager.screen;
import net.northking.cctp.upperComputer.constants.ResponseCmd;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.thread.HarmonyProvider;
import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.HyppiumAgent;
import net.northking.cctp.upperComputer.utils.SessionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import javax.websocket.Session;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author : yineng.huang
* @date : 2024/10/22 17:03
*/
public class HarmonyScreenResponseThread extends ImageScreenResponse implements HyppiumAgent.OnCaptureData{
private final Logger logger = LoggerFactory.getLogger(HarmonyScreenResponseThread.class);
private HarmonyDevice harmonyDevice;
public HarmonyScreenResponseThread(Session session, HarmonyDevice harmonyDevice){
this.webSessions.add(session);
this.harmonyDevice = harmonyDevice;
this.deviceId = harmonyDevice.getHdcDevice().getConnectKey();
}
public HarmonyScreenResponseThread(HarmonyDevice harmonyDevice){
this.harmonyDevice = harmonyDevice;
this.deviceId = harmonyDevice.getHdcDevice().getConnectKey();
}
@Override
public void changeScreenSize() {
}
@Override
public void stopFetchPic() {
HarmonyProvider provider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(this.deviceId);
if (null != provider) {
boolean success = provider.stopCaptureScreenImageStream();
logger.info("停止获取手机【{}】的屏幕的结果:{}", deviceId, success);
}else {
logger.info("未获取到手机【{}】的provider", deviceId);
}
HarmonyDeviceManager.getInstance().removeScreenThread(deviceId);
this.harmonyDevice = null;
}
@Override
public void onCaptureData(byte[] data) {
this.lastData = data;
//有后端会话则给后端推送屏幕图片
if (!CollectionUtils.isEmpty(webSessions)) {
Iterator<Session> iterator = webSessions.iterator();
while (iterator.hasNext()) {
Session webSession = iterator.next();
if (webSession.isOpen()) {
if (sendDeviceStatus) {
Map<String, Object> result = new HashMap<>();
result.put(ResponseCmd.DeviceStatus.STATUS, ResponseCmd.DeviceStatus.CONNECTED);
SessionUtils.sendMessageInitiative(webSession, ResponseCmd.DEVICE_STATUS, deviceId, result, "设备连接失败");
sendDeviceStatus = false;
}
SessionUtils.sendBinary(webSession, this.lastData);
} else {
logger.warn("推送到web端的session已经断开sessionId:{}",webSession.getId());
iterator.remove();
}
}
}
if (null != screenRecorder) {
screenRecorder.addImgToVideo(this.lastData);
}
}
}

View File

@ -0,0 +1,497 @@
package net.northking.cctp.upperComputer.deviceManager.thread;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.deviceManager.thread.util.ThreadJob;
import net.northking.cctp.upperComputer.deviceManager.thread.util.ThreadScope;
import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCConnectStatus;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.HyppiumAgent;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize;
import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
/**
* 鸿
* <br>
* 使
* <br>
*
*/
public class HarmonyProvider implements Closeable,InitDevice {
private static final Logger log = LoggerFactory.getLogger(HarmonyProvider.class);
private static final ThreadScope GLOBAL_PROVIDER_THREAD_SCOPE = new ThreadScope("HarmonyProvider");
/**
*
*/
private static final long RETRY_DELAY = 1000L;
private static final long REQUEST_TIMEOUT = 2000L;
/**
* 鸿
*/
private final HarmonyDevice harmonyDevice;
/**
* Agent
*/
private final HyppiumAgent agent;
/**
* Agent
*/
private HyppiumAgent.Session session;
private final ThreadScope requestScope;
private final ArrayList<ThreadJob> requestJobList = new ArrayList<>();
private StatusListener statusListener = null;
/**
*
* <br>
* null
*/
private HyppiumAgent.OnCaptureData screenCaptureDataCallback = null;
/**
*
*/
private float screenCaptureScale = 1.0f;
/**
* UI
* <br>
* nullUIUI
*/
private HyppiumAgent.OnCaptureData uiActionCaptureDataCallback = null;
private LinkedHashMap<String, Object> deviceInfo;
/**
* @param harmonyDevice 鸿
*/
public HarmonyProvider(HarmonyDevice harmonyDevice) throws IOException {
this.harmonyDevice = harmonyDevice;
this.requestScope = GLOBAL_PROVIDER_THREAD_SCOPE.createChildScope("hm-" + harmonyDevice.getHdcDevice().getConnectKey());
try {
agent = new HyppiumAgent(harmonyDevice.getHdcDevice()); //初始化一次Agent
} catch (IOException e) {
log.error("初始化设备{}的HyppiumAgent失败", harmonyDevice.getHdcDevice().getConnectKey(), e);
throw e;
}
if (!createSession()) {
GLOBAL_PROVIDER_THREAD_SCOPE.launch((job) -> {
init();
}, RETRY_DELAY);
}
deviceInfo = getDeviceInfo();
HarmonyDeviceManager.getInstance().registerDeviceToDeviceManager(deviceInfo); //注册设备到设备管理
}
private void init() {
if (!reinitAgent()) {
GLOBAL_PROVIDER_THREAD_SCOPE.launch((job) -> {
init();
}, RETRY_DELAY);
return;
}
if (!createSession()) {
GLOBAL_PROVIDER_THREAD_SCOPE.launch((job) -> {
init();
}, RETRY_DELAY);
return;
}
if (screenCaptureDataCallback != null) {
startCaptureScreenImageStream(screenCaptureScale, screenCaptureDataCallback);
}
}
private boolean reinitAgent() {
if (harmonyDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED) {
return false;
}
try {
agent.initAgent();
} catch (IOException e) {
log.error("重新初始化设备{}的HyppiumAgent失败", harmonyDevice.getHdcDevice().getConnectKey(), e);
return false;
}
return true;
}
private boolean createSession() {
if (harmonyDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED) {
return false;
}
if (session != null) {
session.close();
session = null;
}
session = agent.createSession();
session.setOnCloseListener(this::onSessionClosed);
return session != null;
}
private void onSessionClosed() {
if (statusListener != null) {
try {
statusListener.onStatusChange(harmonyDevice, Status.UNAVAILABLE);
} catch (Exception e) {
log.error("执行鸿蒙设备{}状态回调时发生异常", harmonyDevice.getHdcDevice().getConnectKey(), e);
}
}
GLOBAL_PROVIDER_THREAD_SCOPE.launch((job) -> {
init();
});
}
@Override
public void close() {
requestScope.depose();
}
/**
*
* <br>
*
*
* @param statusListener
*/
public void setStatusListener(StatusListener statusListener) {
this.statusListener = statusListener;
}
/**
*
*
* @return
*/
public boolean pressBack() {
return scopeRequest(() -> session.pressBack(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return
*/
public boolean pressHome() {
return scopeRequest(() -> session.pressHome(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return
*/
public boolean pressRecentApp() {
return scopeRequest(() -> session.pressRecentApp(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return
*/
public boolean pressPowerKey() {
return scopeRequest(() -> session.pressPowerKey(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return
*/
public boolean pressUpVolume() {
return scopeRequest(() -> session.pressUpVolume(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return
*/
public boolean pressDownVolume() {
return scopeRequest(() -> session.pressDownVolume(REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @param scale
* @return
*/
public boolean pinch(float scale) {
return scopeRequest(() -> session.pinch(scale, REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @param x
* @param y
* @return
*/
public boolean touchDown(int x, int y) {
return scopeRequest(() -> session.touchDown(x, y, REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @param x
* @param y
* @return
*/
public boolean touchMove(int x, int y) {
return scopeRequest(() -> session.touchMove(x, y, REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @param x
* @param y
* @return
*/
public boolean touchUp(int x, int y) {
return scopeRequest(() -> session.touchUp(x, y, REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @param scale
* @param onCaptureData
* @return
*/
public synchronized boolean startCaptureScreenImageStream(float scale, HyppiumAgent.OnCaptureData onCaptureData) {
if (onCaptureData == null) throw new IllegalArgumentException("onCaptureData不能为空");
screenCaptureDataCallback = onCaptureData;
screenCaptureScale = scale;
return scopeRequest(() -> {
if (screenCaptureDataCallback == null) {
return false;
}
try {
return session.startCaptureScreenImageStream(scale, onCaptureData, REQUEST_TIMEOUT);
} catch (HyppiumAgent.CaptureAlreadyRunningException e) {
log.error("鸿蒙设备{}报告已经开启了屏幕图片流", harmonyDevice.getHdcDevice().getConnectKey());
if (screenCaptureDataCallback == null) return false;
if (session.stopCaptureScreenImageStream(screenCaptureDataCallback, REQUEST_TIMEOUT)) {
if (screenCaptureDataCallback == null) return false;
try {
return session.startCaptureScreenImageStream(scale, onCaptureData, REQUEST_TIMEOUT);
} catch (Exception ignored) {
log.error("鸿蒙设备{}报告已经开启了屏幕图片流且尝试自动启动", harmonyDevice.getHdcDevice().getConnectKey());
}
} else {
log.error("鸿蒙设备{}报告已经开启了屏幕图片流,尝试自动停止时失败", harmonyDevice.getHdcDevice().getConnectKey());
}
return false;
}
}, Boolean.class);
}
/**
*
*
* @return
*/
public boolean stopCaptureScreenImageStream() {
if (screenCaptureDataCallback == null) return false;
boolean result = scopeRequest(() -> session.stopCaptureScreenImageStream(screenCaptureDataCallback, REQUEST_TIMEOUT), Boolean.class);
if (result) {
screenCaptureDataCallback = null;
}
return result;
}
/**
* UI
* <br>
* UI
*
* @param onCaptureData UI
* @return
*/
public synchronized boolean startCaptureUiAction(HyppiumAgent.OnCaptureData onCaptureData) {
if (onCaptureData == null) throw new IllegalArgumentException("onCaptureData不能为空");
uiActionCaptureDataCallback = onCaptureData;
return scopeRequest(() -> {
if (uiActionCaptureDataCallback == null) {
return false;
}
try {
return session.startCaptureUiAction(onCaptureData, REQUEST_TIMEOUT);
} catch (HyppiumAgent.CaptureAlreadyRunningException e) {
log.error("鸿蒙设备{}报告已经开启了UI事件捕捉", harmonyDevice.getHdcDevice().getConnectKey());
if (uiActionCaptureDataCallback == null) return false;
if (session.stopCaptureUiAction(uiActionCaptureDataCallback, REQUEST_TIMEOUT)) {
if (uiActionCaptureDataCallback == null) return false;
try {
return session.startCaptureUiAction(onCaptureData, REQUEST_TIMEOUT);
} catch (Exception ignored) {
log.error("鸿蒙设备{}报告已经开启了UI事件捕捉", harmonyDevice.getHdcDevice().getConnectKey());
}
} else {
log.error("鸿蒙设备{}报告已经开启了UI事件捕捉尝试自动停止时失败", harmonyDevice.getHdcDevice().getConnectKey());
}
}
return false;
}, Boolean.class);
}
/**
* UI
*
* @return
*/
public boolean stopCaptureUiAction() {
if (uiActionCaptureDataCallback == null) return false;
boolean result = scopeRequest(() -> session.stopCaptureUiAction(uiActionCaptureDataCallback, REQUEST_TIMEOUT), Boolean.class);
if (result) {
uiActionCaptureDataCallback = null;
}
return result;
}
/**
* UI
*
* @return UInull
*/
public UiComponent captureLayout() {
return scopeRequest(() -> session.captureLayout(REQUEST_TIMEOUT), UiComponent.class);
}
/**
*
*
* @param direction 00°190°2180°3270°
* @return
*/
public boolean setDisplayRotation(int direction) {
if (direction < 0 || direction > 3) {
throw new IllegalArgumentException("屏幕方向参数错误应该在0 - 3 之间");
}
return scopeRequest(() -> session.setDisplayRotation(direction, REQUEST_TIMEOUT), Boolean.class);
}
/**
*
*
* @return -100°190°2180°3270°
*/
public int getDisplayRotation() {
return scopeRequest(() -> session.getDisplayRotation(REQUEST_TIMEOUT), Integer.class);
}
/**
*
*
* @return null
*/
public DisplaySize getScreenSize() {
return scopeRequest(() -> session.getScreenSize(REQUEST_TIMEOUT), DisplaySize.class);
}
private <T> T scopeRequest(DoSessionRequestBlock<T> block, Class<T> resultClass) {
ThreadJob threadJob = requestScope.launch((job) -> {
T result = block.request();
while (job.isValid() && (session == null || session.isClosed())) {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
break;
}
if (session != null && !session.isClosed()) {
result = block.request();
}
}
job.setResult(result);
});
synchronized (requestJobList) {
requestJobList.add(threadJob);
}
try {
threadJob.join();
} catch (InterruptedException e) {
threadJob.setResult(null);
}
synchronized (requestJobList) {
requestJobList.remove(threadJob);
}
return threadJob.getResult(resultClass);
}
@Override
public LinkedHashMap<String, Object> getDeviceInfo() throws IOException {
LinkedHashMap<String, Object> deviceInfo = new LinkedHashMap<>();
//region 设备号
deviceInfo.put("serial", harmonyDevice.getHdcDevice().getConnectKey());
//region 品牌
deviceInfo.put("manufacturer", "HUAWEI");
//region 型号 todo:后面得改
deviceInfo.put("model", "mate60");
deviceInfo.put("product", "mate60");
//region 平台
deviceInfo.put("platform", "harmony");
int rotation = getDisplayRotation();
deviceInfo.put("rotation", rotation);
DisplaySize screenSize = getScreenSize();
deviceInfo.put("width", screenSize.width);
deviceInfo.put("height", screenSize.height);
deviceInfo.put("isHarmony", true);
deviceInfo.put("scale", 1.0);
return deviceInfo;
}
@Override
public void exitDeviceInit() {
}
interface DoSessionRequestBlock<T> {
T request();
}
/**
*
* <br>
*
*/
public synchronized void giveUp() {
synchronized (requestJobList) {
for (ThreadJob job : requestJobList) {
job.cancel();
}
requestJobList.clear();
}
}
public enum Status {
AVAILABLE, UNAVAILABLE;
}
public interface StatusListener {
void onStatusChange(HarmonyDevice device, Status status);
}
}

View File

@ -0,0 +1,15 @@
package net.northking.cctp.upperComputer.deviceManager.thread;
import java.io.IOException;
import java.util.LinkedHashMap;
/**
* @author : yineng.huang
* @date : 2024/10/15 16:18
*/
public interface InitDevice {
public LinkedHashMap<String, Object> getDeviceInfo() throws IOException;
public void exitDeviceInit();
}

View File

@ -0,0 +1,16 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
import java.nio.ByteBuffer;
public interface IIOJob {
void write(ByteBuffer buffer);
void markFinish();
boolean isFinish();
Object getAttachment();
void setAttachment(Object attachment);
}

View File

@ -0,0 +1,248 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import static net.northking.cctp.upperComputer.deviceManager.thread.util.IOScope.BUFFER_SIZE;
/**
* IO<br>
* Socket
*/
public class IOJob implements IIOJob, Runnable {
private final InputStream inputStream;
private final ThreadScope handleScope;
private final OnReadBufferCallback<IOJob> callback;
protected final ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
private boolean finish = false;
private final Socket socket;
private Object attachment = null;
private final Thread readThread = new Thread(this);
/**
* markReadDone
*/
private final Object waitDoReadLock = new Object();
protected IOJob(Socket socket, ThreadScope handleScope, OnReadBufferCallback<IOJob> callback) throws IOException {
this.socket = socket;
this.inputStream = socket.getInputStream();
this.handleScope = handleScope;
this.callback = callback;
readThread.start();
}
protected IOJob(InputStream inputStream, ThreadScope handleScope, OnReadBufferCallback<IOJob> callback) {
this.socket = null;
this.inputStream = inputStream;
this.handleScope = handleScope;
this.callback = callback;
readThread.start();
}
public ThreadScope getHandleScope() {
return handleScope;
}
public OnReadBufferCallback<IOJob> getCallback() {
return callback;
}
/**
* byteBufferbyteBuffer
*/
private void markReadDone() {
synchronized (waitDoReadLock) {
waitDoReadLock.notifyAll();
}
}
/**
* <br>
* IOScope
*/
public void markFinish() {
finish = true;
if (callback instanceof BufferToLineReader) {
handleScope.launch((job) -> {
((BufferToLineReader<IOJob>) callback).writeRemainText(this);
});
}
if (!readThread.isInterrupted()) {
readThread.interrupt();
}
if (socket != null) {
try {
socket.close();
} catch (IOException ignored) {
}
}
handleScope.ioJobFinish(this);
}
/**
*
*
* @return
*/
public boolean isFinish() {
return finish;
}
@Override
public void write(ByteBuffer buffer) {
throw new RuntimeException("写入请使用ThreadScope进行");
}
@Override
public void run() {
int count = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while (!handleScope.isDeposed() && !finish && !readThread.isInterrupted()) {
try {
count = inputStream.read(buffer);
} catch (IOException e) {
handleScope.launch((job) -> {
callback.onRead(this, null, e);
markReadDone();
});
break;
}
if (count < 1) {
System.out.println("======count:" + count);
handleScope.launch((job) -> {
callback.onRead(this, null, null);
markReadDone();
});
break;
}
byteBuffer.clear();
byteBuffer.put(buffer, 0, count);
handleScope.launch((job) -> {
callback.onRead(this, byteBuffer, null);
markReadDone();
});
try {
synchronized (waitDoReadLock) {
waitDoReadLock.wait();
}
} catch (InterruptedException e) {
break;
}
}
byteBuffer.clear();
markFinish();
}
@Override
public Object getAttachment() {
return attachment;
}
@Override
public void setAttachment(Object attachment) {
this.attachment = attachment;
}
/**
* <br>
* onReadThreadScope
*/
public interface OnReadBufferCallback<IIOJob> {
/**
*
*
* @param buffer buffernull
* @param e null
*/
void onRead(IIOJob job, ByteBuffer buffer, Exception e);
}
/**
* <br>
* \n<br>
*
*/
public interface OnReadLineCallback<JOB> {
/**
*
*
* @param job job
* @param line
* @param e null
*/
void onLine(JOB job, String line, Exception e);
}
/**
* <br>
* ThreadScopeIOScope<br>
*
*/
public static class BufferToLineReader<IIOJob> implements OnReadBufferCallback<IIOJob> {
private final ByteArrayOutputStream strOutputStream = new ByteArrayOutputStream(BUFFER_SIZE);
private String holdString = null;
private final OnReadLineCallback<IIOJob> callback;
public BufferToLineReader(OnReadLineCallback<IIOJob> callback) {
this.callback = callback;
}
@Override
public void onRead(IIOJob job, ByteBuffer buffer, Exception e) {
if (buffer != null && buffer.position() > 0) {
strOutputStream.write(buffer.array(), 0, buffer.position());
if (holdString == null) {
holdString = strOutputStream.toString();
} else {
holdString += strOutputStream.toString();
}
strOutputStream.reset();
int wrapPosition;
do {
wrapPosition = holdString.indexOf('\n');
if (wrapPosition >= 0) {
String readLine = holdString.substring(0, wrapPosition).trim(); //需要去除头尾不可见字符,否则一些控制字符将会错误的应用到终端输出效果上(例如行消失不见了,但内容实际上又是有的)
holdString = holdString.substring(wrapPosition + 1);
callback.onLine(job, readLine, e);
}
} while (wrapPosition >= 0);
} else if (e != null) {
callback.onLine(job, null, e);
}
}
/**
*
*/
protected void writeRemainText(IIOJob job) {
if (holdString != null) {
String text = holdString.trim();
if (text.length() > 0) {
callback.onLine(job, holdString, null);
}
holdString = null;
}
}
}
}

View File

@ -0,0 +1,9 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
/**
* IO<br>
* 使线IO
*/
public class IOScope {
public static final int BUFFER_SIZE = 163840;
}

View File

@ -0,0 +1,9 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
/**
* 线<br>
*
*/
public interface LaunchBlock {
void run(ThreadJob job);
}

View File

@ -0,0 +1,124 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
/**
* 线<br>
* 线
*/
public class ThreadJob {
private final LaunchBlock launchBlock;
private final Object lock = new Object();
private final long timeToRun;
private Object result = null;
ThreadJob(LaunchBlock block) {
this(block, 0L);
}
public ThreadJob(LaunchBlock launchBlock, long timeToRun) {
this.launchBlock = launchBlock;
this.timeToRun = timeToRun;
}
/**
*
*/
private boolean valid = true;
/**
*
*/
private boolean finish = false;
public boolean isTimeToRun() {
return System.currentTimeMillis() >= timeToRun && valid;
}
public void run() {
try {
launchBlock.run(this);
finish = true;
synchronized (lock) {
lock.notifyAll();
}
} catch (Exception e) {
synchronized (lock) {
lock.notifyAll();
}
finish = true;
throw e;
}
}
/**
*
*
* @throws InterruptedException
*/
public void join() throws InterruptedException {
if (finish) return;
synchronized (lock) {
lock.wait();
}
}
/**
* <br>
*
*
* @return
*/
public boolean isValid() {
return valid;
}
/**
*
* 线
*/
public void cancel() {
valid = false;
synchronized (lock) {
lock.notifyAll();
}
}
/**
* <br>
* 使
*
* @return
*/
public boolean keepRun() {
return valid && !finish;
}
/**
* launch
*
* @param result
*/
public void setResult(Object result) {
if (!valid || finish) return;
this.result = result;
}
/**
*
*
* @param clazz Class
* @param <T>
* @return null
*/
public <T> T getResult(Class<T> clazz) {
if (result == null) return null;
if (result.getClass() != clazz) {
return null;
}
//noinspection unchecked
return (T) result;
}
}

View File

@ -0,0 +1,250 @@
package net.northking.cctp.upperComputer.deviceManager.thread.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingDeque;
/**
* 线<br>
* 线
*/
public class ThreadScope implements Runnable {
private static final Logger log = LoggerFactory.getLogger(ThreadScope.class);
/**
* ThreadScope<br>
* <br>
* depose
*/
public static final ThreadScope Default = new ThreadScope("default");
/**
* 线
*/
private final String name;
/**
* 线
*/
private final Thread thread = new Thread(this);
/**
*
*/
private final LinkedBlockingDeque<ThreadJob> jobQueue = new LinkedBlockingDeque<>();
private final LinkedList<ThreadScope> childScopes = new LinkedList<>();
private final ThreadScope parentScope;
/**
*
*/
private boolean deposed = false;
/**
* ioJob
*/
private final ArrayList<IOJob> ioJobs = new ArrayList<>();
private final Object launchLock = new Object();
public ThreadScope(String name) {
this(name, null);
}
public ThreadScope(String name, ThreadScope parentScope) {
this.name = name;
this.parentScope = parentScope;
thread.setName("TS-" + name);
thread.setDaemon(true);
thread.start();
}
public ThreadJob launch(LaunchBlock block) {
return launch(block, 0L);
}
public ThreadJob launch(LaunchBlock block, long delay) {
if (deposed) return null;
synchronized (launchLock) {
ThreadJob job = new ThreadJob(block, System.currentTimeMillis() + delay);
jobQueue.offer(job);
return job;
}
}
@Override
public void run() {
while (!thread.isInterrupted() && !deposed) {
ThreadJob job;
try {
job = jobQueue.take();
if (!job.isTimeToRun()) {
jobQueue.put(job);
if (jobQueue.size() == 1) {
Thread.sleep(1L);
}
continue;
}
} catch (InterruptedException e) {
break;
}
if (job.isValid()) {
try {
job.run();
} catch (Exception e) {
log.error("执行job时出错", e);
}
}
}
}
public String getName() {
return name;
}
/**
* 线<br>
*
* 线
*/
public void depose() {
depose(false);
}
private void depose(boolean byParent) {
if (deposed) return;
deposed = true;
synchronized (childScopes) {
childScopes.forEach((childScope) -> childScope.depose(true));
childScopes.clear();
}
if (!byParent && parentScope != null) {
parentScope.childDepose(this);
}
if (!thread.isInterrupted()) {
thread.interrupt();
}
while (!jobQueue.isEmpty()) {
ThreadJob job;
try {
job = jobQueue.take();
} catch (InterruptedException e) {
break;
}
if (job.isValid()) {
job.cancel();
}
}
while (!ioJobs.isEmpty()) {
ArrayList<IOJob> currentIoJobs;
synchronized (ioJobs) {
currentIoJobs = new ArrayList<>(ioJobs);
}
currentIoJobs.forEach(IOJob::markFinish);
}
}
private void checkNotDeposed() {
if (deposed) throw new IllegalStateException("net.northking.cctp.util.ThreadScope is deposed.");
}
/**
* 线<br>
* 线线<br>
* 线线
*/
public ThreadScope createChildScope(String name) {
checkNotDeposed();
ThreadScope threadScope = new ThreadScope(name, this);
synchronized (childScopes) {
childScopes.add(threadScope);
}
return threadScope;
}
/**
* 线使线
*
* @param childScope 线
*/
private void childDepose(ThreadScope childScope) {
synchronized (childScopes) {
childScopes.remove(childScope);
}
}
public boolean isDeposed() {
return deposed;
}
/**
* Socket线<br>
* @param socket Socket
* @param callback
* @return job
* @throws IOException IO
*/
public IOJob readSocketBuffer(Socket socket, IOJob.OnReadBufferCallback<IOJob> callback) throws IOException {
IOJob ioJob = new IOJob(socket, this, callback);
synchronized (ioJobs) {
ioJobs.add(ioJob);
}
return ioJob;
}
/**
* InputStreamSocket线<br>
* @param inputStream InputStream
* @param callback
* @return job
* @throws IOException IO
*/
public IOJob readSocketBuffer(InputStream inputStream, IOJob.OnReadBufferCallback<IOJob> callback) throws IOException {
IOJob ioJob = new IOJob(inputStream, this, callback);
synchronized (ioJobs) {
ioJobs.add(ioJob);
}
return ioJob;
}
/**
* Socket
*
* @param socket Socket
* @param callback
* @return IO
*/
public IOJob readSocketLine(Socket socket, IOJob.OnReadLineCallback<IOJob> callback) throws IOException {
IOJob.BufferToLineReader reader = new IOJob.BufferToLineReader<>(callback);
return readSocketBuffer(socket, reader);
}
/**
* InputStream
*
* @param inputStream InputStream
* @param callback
* @return IO
*/
public IOJob readStreamLine(InputStream inputStream, IOJob.OnReadLineCallback<IOJob> callback) throws IOException {
IOJob.BufferToLineReader reader = new IOJob.BufferToLineReader<>(callback);
return readSocketBuffer(inputStream, reader);
}
protected void ioJobFinish(IOJob ioJob) {
synchronized (ioJobs) {
ioJobs.remove(ioJob);
}
}
}

View File

@ -0,0 +1,269 @@
package net.northking.cctp.upperComputer.driver.harmony;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCDevice;
import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCSession;
import net.northking.cctp.upperComputer.driver.harmony.hdc.Hdc;
import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent;
import net.northking.cctp.upperComputer.driver.protocol.packet.CodecUtils;
import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 鸿
*/
public class HarmonyDevice implements ICommandData {
private static final Logger log = LoggerFactory.getLogger(HarmonyDevice.class);
/**
* UI
*/
private static final String DEVICE_UI_TREE_PATH = "/data/local/tmp/ui.json";
/**
*
*/
private static final String DEVICE_SCREENSHOT_PATH = "/data/local/tmp/screen.png";
/**
* Shell
* <br>
* kill/killall
*/
private static final String ENABLE_TEST_MODE_SHELL = "param set persist.ace.testmode.enabled 1";
private HDCDevice hdcDevice;
public HarmonyDevice(HDCDevice hdcDevice) {
this.hdcDevice = hdcDevice;
}
public HDCDevice getHdcDevice() {
return hdcDevice;
}
public void setHdcDevice(HDCDevice hdcDevice) {
this.hdcDevice = hdcDevice;
}
/**
*
* <br>
* kill/killall
*
* @return
*/
public boolean enableTestMode() {
String outputText = "";
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, ENABLE_TEST_MODE_SHELL)) {
outputText = session.readAllLines();
log.debug("切换测试模式输出:{}", outputText);
}
boolean result = outputText.contains("Set parameter persist.ace.testmode.enabled 1 success");
if (!result) {
log.error("鸿蒙设备{}切换隐藏测试模式失败:{}", hdcDevice.getConnectKey(), outputText);
}
return result;
}
/**
*
* <br>
* shelluitest
*
* @param x x
* @param y y
* @param text
* @return
*/
public boolean inputText(int x, int y, String text) {
text = text.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("`", "\\`")
// .replace("'", "\\'")
.replace("$", "\\$");
String outputText = "";
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, "uitest uiInput inputText " + x + " " + y + " \"" + text + "\"")) {
outputText = session.readAllLines();
log.debug("设备{}在({},{}))文本输入{}输出{}", hdcDevice.getConnectKey(), x, y, text, outputText);
}
boolean result = outputText.contains("No Error");
if (!result) {
log.debug("设备{}在({},{}))文本输入失败{}输出{}", hdcDevice.getConnectKey(), x, y, text, outputText);
}
return result;
}
/**
*
* <br>
*
* <br>
* shell
*/
public boolean keyEvent(List<Integer> keyCode) {
StringBuilder shellBuilder = new StringBuilder("uitest uiInput keyEvent");
for (int k : keyCode) {
shellBuilder.append(" ");
shellBuilder.append(k);
}
String outputText = "";
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, shellBuilder.toString())) {
outputText = session.readAllLines();
log.debug("设备{}按键{}输出{}", hdcDevice.getConnectKey(), keyCode, outputText);
}
boolean result = outputText.contains("No Error");
if (!result) {
log.debug("设备{}按键失败{},输出{}", hdcDevice.getConnectKey(), keyCode, outputText);
}
return result;
}
/**
* UI
*
* @return UI
*/
public ArrayList<UiComponent> getUiTree() {
ArrayList list = new ArrayList<UiComponent>();
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, "uitest dumpLayout -p " + DEVICE_UI_TREE_PATH + " -i")) {
String output;
StringBuilder sb = new StringBuilder();
while ((output = session.readLine()) != null) {
sb.append(output);
}
output = sb.toString();
if (output.contains("DumpLayout saved to:" + DEVICE_UI_TREE_PATH)) {
File file = HarmonyDeviceManager.getWorkTmpFile(hdcDevice.getConnectKey() + "-ui.json");
boolean fileResult = Hdc.getInstance().receiveFile(hdcDevice, DEVICE_UI_TREE_PATH, file);
if (fileResult) {
try (FileReader reader = new FileReader(file)) {
list = HarmonyDeviceManager.gson.fromJson(reader, new TypeToken<ArrayList<UiComponent>>() {
}.getType());
} catch (IOException e) {
log.error("读取鸿蒙设备{}的UI数据文件时发生IO错误", hdcDevice.getConnectKey(), e);
} catch (JsonParseException e) {
log.error("解析鸿蒙设备{}的UI数据文件时发生错误", hdcDevice.getConnectKey(), e);
}
if (!file.delete()) {
log.warn("删除鸿蒙设备{}的UI数据文件失败", hdcDevice.getConnectKey());
}
}
}
}
return list;
}
/**
* <br>
* png
*
* @return png0
*/
public byte[] takeScreenshot() {
byte[] result = new byte[0];
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, "uitest screenCap -p " + DEVICE_SCREENSHOT_PATH)) {
String output;
StringBuilder sb = new StringBuilder();
while ((output = session.readLine()) != null) {
sb.append(output);
}
output = sb.toString();
if (output.contains("ScreenCap saved to " + DEVICE_SCREENSHOT_PATH)) {
File file = HarmonyDeviceManager.getWorkTmpFile(hdcDevice.getConnectKey() + "-screenshot.png");
boolean fileResult = Hdc.getInstance().receiveFile(hdcDevice, DEVICE_SCREENSHOT_PATH, file);
if (fileResult) {
try (FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
bos.write(buffer, 0, length);
}
result = bos.toByteArray();
} catch (IOException e) {
log.error("读取鸿蒙设备{}的屏幕截图临时文件时发生IO错误", hdcDevice.getConnectKey(), e);
}
if (!file.delete()) {
log.warn("删除鸿蒙设备{}的屏幕截图临时文件失败", hdcDevice.getConnectKey());
}
}
}
}
return result;
}
/**
* <br>
* png
*
* @return png0
*/
public File takeScreenshotForFile(String fileName) {
boolean success = false;
int time = 1;
while (!success && time <= 5) { //成功或者10秒
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, "uitest screenCap -p " + DEVICE_SCREENSHOT_PATH)) {
String output;
StringBuilder sb = new StringBuilder();
while ((output = session.readLine()) != null) {
sb.append(output);
}
output = sb.toString();
log.debug("设备【{}】第{}次截图到设备的输出:{}", hdcDevice.getConnectKey(),time,output);
if (output.contains("ScreenCap saved to " + DEVICE_SCREENSHOT_PATH)) {
log.warn("设备【{}】第{}次截图成功............",hdcDevice.getConnectKey(),time);
success = true;
break;
} else {
log.warn("设备【{}】第{}次截图失败,重新尝试............",hdcDevice.getConnectKey(),time);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.warn("设备【{}】不截图了", hdcDevice.getConnectKey(), e);
return null;
}
time++;
}
}
}
if (success) {
File file = new File(fileName);
boolean fileResult = Hdc.getInstance().receiveFile(hdcDevice, DEVICE_SCREENSHOT_PATH, file);
if (fileResult) {
return file;
}
} else {
log.warn("设备【{}】截了5次图都没有一次成功过", hdcDevice.getConnectKey());
}
return null;
}
@Override
public String toString() {
return "HarmonyDevice{connectKey=" + hdcDevice.getConnectKey() + " status:" + hdcDevice.getConnectStatus() + "}";
}
@Override
public void buildFromInput(DataInput dataInput) {
throw new RuntimeException("不支持的操作,请勿调用此方法");
}
@Override
public void encodeToOutput(DataOutput dataOutput) throws IOException {
CodecUtils.writeText(dataOutput, hdcDevice.getConnectKey());
CodecUtils.writeText(dataOutput, hdcDevice.getConnectType().name());
CodecUtils.writeText(dataOutput, hdcDevice.getConnectStatus().name());
CodecUtils.writeText(dataOutput, hdcDevice.getLocation());
}
}

View File

@ -0,0 +1,13 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
public class DeviceActionResult {
final String connectKey;
final boolean isSuccess;
final String message;
public DeviceActionResult(String connectKey, boolean isSuccess, String message) {
this.connectKey = connectKey;
this.isSuccess = isSuccess;
this.message = message;
}
}

View File

@ -0,0 +1,184 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class ForwardRule {
private final Logger logger = LoggerFactory.getLogger(ForwardRule.class);
/**
* key
*/
private final String connectKey;
/**
* Scheme
*/
private final ListenScheme deviceScheme;
/**
* scheme
*/
private final String deviceContent;
/**
* Scheme
*/
private final ListenScheme localScheme;
/**
* scheme
*/
private final String localContent;
/**
* <br>
* <br>
*
*/
private final boolean reverse;
public ForwardRule(String connectKey, ListenScheme deviceScheme, String deviceContent, ListenScheme localScheme, String localContent, boolean reverse) {
this.connectKey = connectKey;
this.deviceScheme = deviceScheme;
this.deviceContent = deviceContent;
this.localScheme = localScheme;
this.localContent = localContent;
this.reverse = reverse;
}
public ForwardRule(String fullTaskString) {
String[] baseSplit = fullTaskString.split(" {4}");
if (baseSplit.length != 3) {
throw new IllegalArgumentException("无法处理的端口转发规则内容[无法处理基础分割]" + fullTaskString);
}
this.connectKey = baseSplit[0];
this.reverse = baseSplit[2].equals("[Reverse]");
String[] baseTaskInfo = baseSplit[1].split(" ");
if (baseTaskInfo.length != 2) {
throw new IllegalArgumentException("无法处理的端口转发规则内容[无法处理端口转发任务信息分割]" + fullTaskString);
}
String[] firstInfos = baseTaskInfo[0].split(":");
if (firstInfos.length != 2) {
throw new IllegalArgumentException("无法处理第一个转发规则内容:" + fullTaskString);
}
String[] secondInfos = baseTaskInfo[1].split(":");
if (secondInfos.length != 2) {
throw new IllegalArgumentException("无法处理第二个转发规则内容:" + fullTaskString);
}
if (this.reverse) {
this.deviceScheme = ListenScheme.fromString(firstInfos[0]);
this.deviceContent = firstInfos[1];
this.localScheme = ListenScheme.fromString(secondInfos[0]);
localContent = secondInfos[1];
} else {
this.localScheme = ListenScheme.fromString(firstInfos[0]);
this.localContent = firstInfos[1];
this.deviceScheme = ListenScheme.fromString(secondInfos[0]);
deviceContent = secondInfos[1];
}
}
public boolean isReverse() {
return reverse;
}
/**
*
* @return
*/
public String deviceString() {
return deviceScheme.getScheme() + ":" + deviceContent;
}
/**
*
*
* @return
*/
public String localString() {
return localScheme.getScheme() + ":" + localContent;
}
/**
*
*
* @return
*/
public String taskString() {
if (reverse) {
return deviceString() + " " + localString();
} else {
return localString() + " " + deviceString();
}
}
public String getConnectKey() {
return connectKey;
}
public ListenScheme getDeviceScheme() {
return deviceScheme;
}
public String getDeviceContent() {
return deviceContent;
}
public ListenScheme getLocalScheme() {
return localScheme;
}
public String getLocalContent() {
return localContent;
}
@Override
public String toString() {
return "ForwardRule{" +
"connectKey='" + connectKey + '\'' +
", deviceScheme=" + deviceScheme +
", deviceContent='" + deviceContent + '\'' +
", localScheme=" + localScheme +
", localContent='" + localContent + '\'' +
", reverse=" + reverse +
'}';
}
/**
*
*/
public enum ListenScheme {
TCP("tcp"),
LOCAL_FILE_SYSTEM("localfilesystem"),
LOCAL_RESERVED("localreserved"),
LOCAL_ABSTRACT("localabstract"),
DEV("dev"),
JDWP("jdwp");
private final String scheme;
ListenScheme(String s) {
this.scheme = s;
}
public String getScheme() {
return scheme;
}
public static ListenScheme fromString(String s) {
for (ListenScheme listenScheme : values()) {
if (listenScheme.getScheme().equals(s)) {
return listenScheme;
}
}
throw new IllegalArgumentException("Unknown Scheme");
}
}
}

View File

@ -0,0 +1,34 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
public enum HDCConnectStatus {
/**
*
*/
OFFLINE,
/**
*
*/
CONNECTED,
/**
*
*/
UNKNOWN;
public static HDCConnectStatus getHDCConnectStatus(String status) {
HDCConnectStatus hdcConnectStatus = null;
switch (status.toUpperCase()) {
case "OFFLINE" :
hdcConnectStatus = OFFLINE;
break;
case "CONNECTED" :
hdcConnectStatus = CONNECTED;
break;
default :
hdcConnectStatus = UNKNOWN;
break;
};
return hdcConnectStatus;
}
}

View File

@ -0,0 +1,45 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
/**
* HDC
*/
public enum HDCConnectType {
/**
* TCP
*/
TCP,
/**
* USB
*/
USB,
/**
*
*/
UART,
/**
*
*/
UNKNOWN;
public static HDCConnectType getConnectType(String type) {
HDCConnectType hdcConnectType = null;
switch (type.toUpperCase()) {
case "TCP" :
hdcConnectType = TCP;
break;
case "USB" :
hdcConnectType = USB;
break;
case "UART" :
hdcConnectType = UART;
break;
default :
hdcConnectType = UNKNOWN;
break;
}
return hdcConnectType;
}
}

View File

@ -0,0 +1,90 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
/**
* HDC
*/
public class HDCDevice {
/**
* key
*/
private final String connectKey;
/**
*
*/
private final HDCConnectType connectType;
/**
*
*/
private HDCConnectStatus connectStatus;
/**
*
*/
private final String location;
public HDCDevice(String connectKey, HDCConnectType connectType, HDCConnectStatus connectStatus, String location) {
this.connectKey = connectKey;
this.connectType = connectType;
this.connectStatus = connectStatus;
this.location = location;
}
public String getConnectKey() {
return connectKey;
}
public HDCConnectType getConnectType() {
return connectType;
}
public HDCConnectStatus getConnectStatus() {
return connectStatus;
}
public void setConnectStatus(HDCConnectStatus connectStatus) {
this.connectStatus = connectStatus;
}
public String getLocation() {
return location;
}
/**
*
*
* @return
*/
public String getProtocol() {
return "hdc";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HDCDevice hdcDevice = (HDCDevice) o;
return connectKey.equals(hdcDevice.connectKey) && connectType == hdcDevice.connectType && connectStatus == hdcDevice.connectStatus && location.equals(hdcDevice.location);
}
@Override
public int hashCode() {
int result = connectKey.hashCode();
result = 31 * result + connectType.hashCode();
result = 31 * result + connectStatus.hashCode();
result = 31 * result + location.hashCode();
return result;
}
@Override
public String toString() {
return "HDCDevice{" +
"connectKey='" + connectKey + '\'' +
", connectType=" + connectType +
", connectStatus=" + connectStatus +
", location='" + location + '\'' +
'}';
}
}

View File

@ -0,0 +1,17 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
import java.util.ArrayList;
/**
*
* <br>
*
*/
public interface HDCDeviceListener {
/**
*
*
* @param currentDevices
*/
void onDeviceChanged(ArrayList<HDCDevice> currentDevices);
}

View File

@ -0,0 +1,136 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
/**
* HDCHDC
* <br>
* 0.5DevEco Studio
*/
public class HDCDeviceObserver implements Runnable {
private final Logger log = LoggerFactory.getLogger(HDCDeviceObserver.class);
private final Thread thread = new Thread(this);
private HDCSession hdcSession = null;
private ArrayList<HDCDevice> lastDevices = new ArrayList<>();
private final ArrayList<HDCDeviceListener> listeners = new ArrayList<>();
HDCDeviceObserver() {
thread.setName("HDCDeviceObserver");
}
/**
* HDC
*/
public void start() {
if (thread.isInterrupted()) throw new IllegalStateException("不能重新启动已经停止的设备监控");
thread.start();
}
/**
* HDC
*/
public void stop() {
if (hdcSession != null) {
hdcSession.close();
}
if (!thread.isInterrupted()) {
thread.interrupt();
}
synchronized (listeners) {
listeners.clear();
}
}
/**
* HDC
*
* @param listener
*/
public void addHDCDeviceListener(HDCDeviceListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* HDC
*
* @param listener
*/
public void removeHDCDeviceListener(HDCDeviceListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
@Override
public void run() {
while (!thread.isInterrupted()) {
if (hdcSession == null) {
hdcSession = Hdc.getInstance().createSession();
if (hdcSession == null) {
log.warn("无法创建HDC会话等待5秒重试");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
continue;
}
try {
hdcSession.sendHdcData("alive\0".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("观察HDC设备变化发送长连接指令时发生IO错误", e);
hdcSession = null;
continue;
}
}
ArrayList<HDCDevice> devices = Hdc.getInstance().listTargets(hdcSession);
if (devices == null) {
log.warn("监控设备动态失败无法与HDC进行通信等待10秒……");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
break;
}
continue;
}
boolean hasDifferent = devices.size() != lastDevices.size();
if (!hasDifferent) {
for (int i = 0; i < devices.size(); i++) {
if (!devices.get(i).equals(lastDevices.get(i))) {
hasDifferent = true;
break;
}
}
}
if (hasDifferent) {
synchronized (listeners) {
try {
for (HDCDeviceListener l : listeners) {
l.onDeviceChanged(devices);
}
} catch (Exception e) {
log.error("设备变动监听器发生异常", e);
}
}
}
lastDevices = devices;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
break;
}
}
log.warn("harmony设备监听退出................................");
}
}

View File

@ -0,0 +1,244 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
import net.northking.cctp.upperComputer.utils.InputStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class HDCSession implements Closeable {
private static final long HANDSHAKE_DELAY_MS = 500;
private final Logger log = LoggerFactory.getLogger(HDCSession.class);
private final Socket socket;
protected final InputStream in;
protected final OutputStream out;
private boolean closed = false;
private final ByteBuffer readLengthBuffer = ByteBuffer.allocate(4);
private final ByteBuffer writeLengthBuffer = ByteBuffer.allocate(4);
private int sessionId = 0;
/**
* idnull
*/
private final String connectKey;
HDCSession(Socket socket) throws IOException {
this(socket, null);
}
HDCSession(Socket socket, String connectKey) throws IOException {
this.socket = socket;
this.connectKey = connectKey;
if (socket.isClosed()) throw new IllegalStateException("Socket已经关闭");
in = socket.getInputStream();
out = socket.getOutputStream();
if (!handshake()) {
throw new IOException("hdc握手失败");
}
}
public int getSessionId() {
return sessionId;
}
public boolean isClosed() {
if (socket.isClosed()) return true;
return closed;
}
private void ensureOpen() throws IOException {
if (isClosed()) throw new IOException("与HDC的连接已经关闭");
}
/**
* hdc
* <br>
* \0
* <br>
*
*
* @param command Hdc
* @return
* @throws IOException IO
*/
public String sendHdcCommand(String command) throws IOException {
ensureOpen();
sendHdcData((command + "\0").getBytes(StandardCharsets.UTF_8));
return new String(readHdcData());
}
/**
* HDC
* <br>
* 4+
*
* @return
* @throws IOException
*/
public byte[] readHdcData() throws IOException {
if (isClosed()) return new byte[0];
readLengthBuffer.clear();
if (InputStreamUtils.readNBytes(in,readLengthBuffer.array(), 0, 4) != 4) throw new EOFException();
int length = readLengthBuffer.getInt();
if (length < 0) throw new EOFException();
byte[] readData;
try {
readData = InputStreamUtils.readNBytes(in,length);
} catch (IOException e) {
closed = true;
throw e;
}
return readData;
}
/**
* HDC
* <br>
* shell
*
* @param data
* @throws IOException IO
*/
public void sendHdcData(byte[] data) throws IOException {
sendHdcData(data, 0, data.length);
}
/**
* HDC
* <br>
* shell
*
* @param data
* @param offset
* @param len
* @throws IOException IO
*/
public void sendHdcData(byte[] data, int offset, int len) throws IOException {
if (isClosed()) return;
writeLengthBuffer.clear();
writeLengthBuffer.putInt(len);
try {
out.write(writeLengthBuffer.array());
out.write(data, offset, len);
} catch (IOException e) {
closed = true;
throw e;
}
}
/**
*
*
* @return
*/
private boolean handshake() {
byte[] hostData;
try {
hostData = readHdcData();
} catch (IOException e) {
log.error("读取hdc握手首条数据发生IO错误", e);
close();
return false;
}
if (hostData.length != 44) {
log.error("读取到的hdc握手首条数据长度不为44长度不匹配。读取到的长度为{}", hostData.length);
close();
return false;
}
//检查banner `OHOS HDC`字符串
String banner = new String(hostData, 0, 8);
if (!banner.equals("OHOS HDC")) {
log.error("读取hdc握手数据中的banner时内容与预期不符读取到{}", banner);
close();
return false;
}
ByteBuffer hostDataBuffer = ByteBuffer.wrap(hostData);
hostDataBuffer.position(8);
int authType = hostDataBuffer.getInt();
if (authType != 0 && authType != 72) {
log.error("读取到的hdc握手首条数据中告知了一个不支持的验证类型{}", authType);
close();
return false;
}
sessionId = hostDataBuffer.getInt();
if (connectKey == null) {
//仅保留banner回复hdc
for (int i = 12; i < hostData.length; i++) {
hostData[i] = 0;
}
try {
sendHdcData(hostData);
} catch (IOException e) {
log.error("回复hdc握手信息时发生IO错误", e);
close();
return false;
}
} else {
byte[] connectKeyBytes = connectKey.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(12 + connectKeyBytes.length);
buffer.put("OHOS HDC".getBytes());
buffer.position(buffer.position() + 4);
buffer.put(connectKeyBytes);
try {
sendHdcData(buffer.array());
} catch (IOException e) {
log.error("回复hdc握手信息并同时指定设备时发生IO错误", e);
close();
return false;
}
}
try {
//等待一会因为hdc server端异步处理session状态
Thread.sleep(HANDSHAKE_DELAY_MS);
} catch (InterruptedException e) {
return false;
}
return true;
}
public String readLine() {
String result = null;
try {
result = new String(readHdcData());
} catch (IOException ignored) {
}
return result;
}
/**
*
*
* @return
*/
public String readAllLines() {
StringBuilder result = new StringBuilder();
String line;
while ((line = readLine()) != null) {
result.append(line).append("\n");
}
return result.toString();
}
/**
* session
*/
@Override
public void close() {
closed = true;
try {
socket.close();
} catch (IOException ignored) {
}
}
}

View File

@ -0,0 +1,583 @@
package net.northking.cctp.upperComputer.driver.harmony.hdc;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class Hdc {
private static final Logger log = LoggerFactory.getLogger(Hdc.class);
/**
* HDC
*/
private static String HOST = "127.0.0.1";
/**
* HDC
*/
private static int PORT = 8710;
private static Hdc instance = null;
public static Hdc getInstance() {
if (instance == null) {
instance = new Hdc();
}
return instance;
}
/**
* HDC
*/
// private static String hdcPath = null;
private HDCDeviceObserver hdcDeviceObserver = null;
private Hdc() {
init();
}
public static void setPORT(int PORT) {
Hdc.PORT = PORT;
}
public static void setHOST(String HOST) {
Hdc.HOST = HOST;
}
/**
* hdc-server
*
* @throws FileNotFoundException hdcPath
*/
public static void init() {
if (!startHdcDaemon()) {
throw new RuntimeException("无法启动HDC守护进程");
}
}
/**
* HDC
*
* @return
*/
private static boolean startHdcDaemon() {
ProcessBuilder processBuilder = new ProcessBuilder("hdc", "list","targets");
Process process;
try {
process = processBuilder.start();
} catch (IOException e) {
log.error("启动hdc失败", e);
return false;
}
boolean runResult = false;
try {
runResult = process.waitFor(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("等待hdc启动的过程中被中断");
return false;
}
if (!runResult) {
return false;
}
return process.exitValue() == 0;
}
/**
* HDC
*
* @return HDCnull
*/
public HDCSession createSession() {
Socket socket;
try {
socket = new Socket(HOST, PORT);
} catch (IOException e) {
log.error("创建HDC会话失败连接到hdc时发生IO错误host:{} port:{}", HOST, PORT, e);
return null;
}
HDCSession session;
try {
session = new HDCSession(socket);
} catch (IOException e) {
log.error("创建HDC会话失败连接到hdc后发生IO错误", e);
return null;
}
return session;
}
public HDCSession createSession(HDCDevice hdcDevice) {
Socket socket;
try {
socket = new Socket(HOST, PORT);
} catch (IOException e) {
log.error("创建设备{}的HDC会话失败连接到hdc时发生IO错误host:{} port:{}", hdcDevice.getConnectKey(), HOST, PORT, e);
return null;
}
HDCSession session;
try {
session = new HDCSession(socket, hdcDevice.getConnectKey());
} catch (IOException e) {
log.error("创建HDC会话失败连接到hdc后发生IO错误", e);
return null;
}
return session;
}
/**
* hdc
*
* @return hdcnull
*/
public String version() {
HDCSession session = createSession();
String rawVersion = null;
try {
rawVersion = session.sendHdcCommand("version");
} catch (IOException e) {
session.close();
return null;
}
if (rawVersion == null) {
return null;
}
if (!rawVersion.startsWith("Ver: ")) {
return null;
}
return rawVersion.substring("Ver: ".length());
}
public ArrayList<HDCDevice> listTargets() {
HDCSession session = createSession();
if (session == null) {
return new ArrayList<>();
}
return listTargets(session);
}
/**
* session
*
* @param session HDC
* @return null
*/
public ArrayList<HDCDevice> listTargets(HDCSession session) {
String replyContent;
try {
replyContent = session.sendHdcCommand("list targets -v");
} catch (IOException e) {
log.error("获取设备列表失败连接到hdc后发生IO错误", e);
return null;
}
if (replyContent.length() < 10) return new ArrayList<>();
replyContent = replyContent.replaceAll("\t", " ");
ArrayList<HDCDevice> result = new ArrayList<HDCDevice>();
String[] lines = replyContent.split("\n");
for (String line : lines) {
if (line.length() > 15) {
HDCDevice device = parseDeviceLine(line);
result.add(device);
}
}
return result;
}
/**
*
*
* @param hdcDevice
* @param file
* @param devicePath
* @return
*/
public boolean sendFile(HDCDevice hdcDevice, File file, String devicePath) {
if (!file.exists()) {
log.error("文件不存在");
return false;
}
if (!file.canRead()) {
log.error("文件不可读");
return false;
}
ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "file", "send", file.getAbsolutePath(), devicePath);
Process process;
try {
process = processBuilder.start();
} catch (IOException e) {
log.error("推送文件{}到设备{}失败发生IO错误", file.getAbsolutePath(), hdcDevice.getConnectKey(), e);
return false;
}
//退出码无法判断是否成功始终为0
BufferedReader reader = null;
try {
InputStream inputStream = process.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("[Fail]")) {
log.error("推送文件{}到设备{}失败:{}", file.getAbsolutePath(), hdcDevice.getConnectKey(), line);
return false;
} else if (line.startsWith("FileTransfer finish")) {
return true;
}
}
} catch (IOException e) {
log.warn("中断推送文件{}到设备{},设备上可能存在文件残留", file.getAbsolutePath(), hdcDevice.getConnectKey());
return false;
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
log.error("关闭读取流失败", e);
}
}
}
log.error("推送文件{}到设备{}失败:{}", file.getAbsolutePath(), hdcDevice.getConnectKey(), "未知错误");
return false;
}
/**
*
*
* @param hdcDevice
* @param devicePath
* @param file
* @return
*/
public boolean receiveFile(HDCDevice hdcDevice, String devicePath, File file) {
String localPath = file.getAbsolutePath();
if (file.exists()) {
if (!file.canWrite()) {
String errorReason = "目标文件无法覆盖";
if (file.isDirectory()) {
errorReason = "目标目录无法写入";
}
log.error("从设备{}接收文件{}失败:{}", hdcDevice.getConnectKey(), devicePath, errorReason);
return false;
}
if (file.isDirectory()) {
localPath += "/";
}
} else {
File parentFile = file.getParentFile();
if (parentFile != null) {
if (!parentFile.exists()) {
log.error("从设备{}接收文件{}失败:{},父目录不存在", hdcDevice.getConnectKey(), devicePath, "父目录不存在");
return false;
}
if (!parentFile.canWrite()) {
log.error("从设备{}接收文件{}失败:{},父目录无法写入", hdcDevice.getConnectKey(), devicePath, "父目录无法写入");
return false;
}
}
}
ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "file", "recv", devicePath, localPath);
Process process;
try {
process = processBuilder.start();
} catch (IOException e) {
log.error("从设备{}接收文件{}失败:发生IO错误", hdcDevice.getConnectKey(), devicePath, e);
return false;
}
//退出码无法判断是否成功始终为0
BufferedReader reader = null;
try {
InputStream inputStream = process.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
String result = builder.toString();
log.debug("从手机拉图片打印:{}",result);
if (result.startsWith("[Fail]")) {
log.error("从设备{}接收文件{}失败:{}", hdcDevice.getConnectKey(), devicePath, line);
return false;
} else if (result.contains("FileTransfer finish")) {
return true;
}
} catch (IOException e) {
log.warn("中断从设备{}接收文件{},可能存在文件残留", hdcDevice.getConnectKey(), file.getAbsolutePath());
return false;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("关闭读取流失败", e);
}
}
}
log.error("推送从设备{}接收文件{}:{}",hdcDevice.getConnectKey(), file.getAbsolutePath(), "未知错误");
return false;
}
/**
* hap
*
* @param hdcDevice hap
* @param hapFile hap
* @return
*/
public boolean install(HDCDevice hdcDevice, File hapFile) {
if (!hapFile.exists()) {
log.error("安装失败HAP文件不存在");
return false;
}
if (!hapFile.canRead()) {
log.error("安装失败HAP文件不可读");
return false;
}
ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "install", hapFile.getAbsolutePath());
Process process;
BufferedReader reader = null;
try {
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("[Fail]")) {
log.error("设备{}安装HAP文件失败:{}", hdcDevice.getConnectKey(), line);
return false;
}
}
} catch (IOException e) {
log.error("设备{}安装HAP文件出错:", hdcDevice.getConnectKey(), e);
return false;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.warn("关闭流失败", e);
}
}
}
return true;
}
/**
* Package
*
* @param hdcDevice
* @param packageName
* @param keepData
* @param removeSharedBundle
* @return truefalse
*/
public boolean uninstall(HDCDevice hdcDevice, String packageName, boolean keepData, boolean removeSharedBundle) {
boolean result = false;
try (HDCSession session = createSession(hdcDevice)) {
StringBuilder commandBuilder = new StringBuilder("uninstall ");
if (keepData) {
commandBuilder.append("-k ");
}
if (removeSharedBundle) {
commandBuilder.append("-s ");
}
commandBuilder.append(packageName);
String returnText = null;
try {
returnText = session.sendHdcCommand(commandBuilder.toString());
} catch (IOException e) {
log.error("设备{}卸载HAP文件发生IO错误", hdcDevice.getConnectKey(), e);
}
if (returnText != null) {
result = !returnText.contains("failed");
if (!result) {
log.error("设备{}卸载HAP文件失败{}", hdcDevice.getConnectKey(), returnText);
}
}
}
return result;
}
/**
* list target -vHDCDevice
*
* @param line hdc
* @return HDCDevice
*/
private HDCDevice parseDeviceLine(String line) {
String[] wordList = line.split(" ");
return new HDCDevice(
wordList[0],
HDCConnectType.getConnectType(wordList[2]),
HDCConnectStatus.getHDCConnectStatus(wordList[3]),
wordList[4]
);
}
/**
*
*/
public void startObserveDevice() {
if (hdcDeviceObserver != null) throw new IllegalStateException("上一个HDC设备变化监控没有关闭");
hdcDeviceObserver = new HDCDeviceObserver();
hdcDeviceObserver.start();
}
/**
*
*/
public void stopObserveDevice() {
if (hdcDeviceObserver != null) {
hdcDeviceObserver.stop();
hdcDeviceObserver = null;
}
}
public boolean isObserveDeviceRunning() {
return hdcDeviceObserver != null;
}
public boolean createForward(ForwardRule forwardRule) {
boolean result = false;
HDCDevice hdcDevice = null;
for (HDCDevice device : listTargets()) {
if (device.getConnectKey().equals(forwardRule.getConnectKey())) {
hdcDevice = device;
break;
}
}
if (hdcDevice == null) {
log.warn("创建端口转发时设备{}不存在", forwardRule.getConnectKey());
return false;
}
if (hdcDevice.getConnectStatus() != HDCConnectStatus.CONNECTED) {
log.warn("创建端口转发时设备{}未连接", forwardRule.getConnectKey());
return false;
}
try (HDCSession session = createSession(hdcDevice)) {
String returnText = null;
String command = "fport";
if (forwardRule.isReverse()) {
command = "rport";
}
try {
returnText = session.sendHdcCommand(command + " " + forwardRule.taskString());
} catch (IOException e) {
log.error("创建端口转发失败", e);
}
if (returnText != null && returnText.startsWith("Forwardport result:OK")) {
result = true;
}else{
log.debug("端口转发创建失败,返回:"+returnText);
}
}
return result;
}
/**
*
*
* @return
*/
public ArrayList<ForwardRule> listForward() {
ArrayList<ForwardRule> rules = new ArrayList<ForwardRule>();
try (HDCSession session = createSession()) {
String returnText = null;
try {
returnText = session.sendHdcCommand("fport ls");
} catch (IOException e) {
log.error("获取端口转发记录失败,发送命令 fport ls 失败", e);
}
if (returnText != null) {
String[] lines = returnText.split("\n");
for (String line : lines) {
if (!line.startsWith("[")) {
rules.add(new ForwardRule(line));
}
}
}
}
return rules;
}
/**
*
*
* @param forwardRule
* @return
*/
public boolean removeForwardRule(ForwardRule forwardRule) {
boolean result = false;
try (HDCSession session = createSession()) {
String returnText = null;
try {
returnText = session.sendHdcCommand("fport rm " + forwardRule.taskString());
} catch (IOException e) {
log.error("移除端口转发记录失败,发送命令 fport rm 失败", e);
}
if (returnText != null) {
String[] lines = returnText.split("\n");
for (String line : lines) {
if (line.startsWith("Remove forward ruler success")) {
result = true;
break;
}
}
}
}
return result;
}
/**
* shell
*
* @param hdcDevice
* @param shell nullshell
* @return HDCSessionnullHDCshell
*/
public HDCSession shell(HDCDevice hdcDevice, String shell) {
HDCSession session = createSession(hdcDevice);
if (shell == null || StringUtils.isBlank(shell)) {
try {
session.sendHdcData(("shell\0").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("创建shell会话失败");
session.close();
return null;
}
} else {
try {
session.sendHdcData(("shell " + shell + "\0").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("通过HDC执行shell失败{}", shell);
session.close();
return null;
}
}
return session;
}
public void addHDCDeviceListener(HDCDeviceListener listener) {
HDCDeviceObserver observer = hdcDeviceObserver;
if (observer == null) throw new IllegalStateException("没有开启监控设备变化");
observer.addHDCDeviceListener(listener);
}
public void removeHDCDeviceListener(HDCDeviceListener listener) {
HDCDeviceObserver observer = hdcDeviceObserver;
if (observer == null) throw new IllegalStateException("没有开启监控设备变化");
observer.removeHDCDeviceListener(listener);
}
public boolean isObservingDevice() {
return hdcDeviceObserver != null;
}
}

View File

@ -0,0 +1,7 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
public enum AgentABI {
ARM64,
X86,
UNKNOWN
}

View File

@ -0,0 +1,837 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import net.northking.cctp.upperComputer.driver.harmony.hdc.*;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.action.*;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.CaptureLayoutData;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.ResultData;
import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent;
import net.northking.cctp.upperComputer.utils.InputStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
/**
* Hyppiumagent.so
* <br>
* 鸿uiteststart-daemon singleness/data/local/tmp/agent.so
*/
public class HyppiumAgent {
private static final Logger log = LoggerFactory.getLogger(HyppiumAgent.class);
/**
* Agent
*/
private static final String DEVICE_AGENT_PATH = "/data/local/tmp/agent.so";
/**
*
*/
public static final byte[] PROTOCOL_HEADER = "_uitestkit_rpc_message_head_".getBytes();
/**
*
*/
public static final byte[] PROTOCOL_TAIL = "_uitestkit_rpc_message_tail_".getBytes();
/**
*
* +++
*/
public static final int PROTOCOL_BASIC_LENGTH = ((PROTOCOL_HEADER.length + 4) + 4 + PROTOCOL_TAIL.length);
/**
* exception
*/
private static final String DATA_CAPTURE_ALREADY_RUNNING_PREFIX = "Capture already running:";
/**
* uitestshell
*/
private static final String KILL_UITEST_SHELL = "killall -9 uitest";
/**
* agentshell
*/
private static final String DELETE_AGENT_FILE_SHELL = "rm -f /data/local/tmp/agent.so";
private static final int AGENT_PORT = 8012;
private final HDCDevice hdcDevice;
private final RequestCodeProvider requestCodeProvider = new RequestCodeProvider();
/**
* 访Agent
* <br>
* 8012
*/
private int localPort;
public HyppiumAgent(HDCDevice hdcDevice) throws IOException {
this.hdcDevice = hdcDevice;
initAgent();
}
public void initAgent() throws IOException {
if (!isForwardExists() && !createForward()) {
throw new IOException("无法为设备" + hdcDevice.getConnectKey() + "的HyppiumAgent创建端口转发");
}
if (!ensureAgentIsRunning()) {
throw new IOException("无法确保设备正在执行Agent.so");
}
}
/**
* Agent
* <br>
* Agent
*
* @return Agent
*/
public boolean ensureAgentIsRunning() {
String psContent = "";
try (HDCSession psSession = Hdc.getInstance().shell(hdcDevice, "ps -ef | grep uitest")) {
psContent = psSession.readAllLines();
}
if (psContent.contains("uitest start-daemon singleness")) {
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, KILL_UITEST_SHELL)) {
log.debug("鸿蒙设备{}杀死uitest输出{}", hdcDevice.getConnectKey(), session.readAllLines());
}
}
//没有Agent正在运行为设备准备文件
//首先确认设备CPU架构
String archContent = "";
try (HDCSession archSession = Hdc.getInstance().shell(hdcDevice, "file /bin/uitest")) {
archContent = archSession.readAllLines();
}
AgentABI abi = AgentABI.UNKNOWN;
if (archContent.contains("aarch64.so")) {
abi = AgentABI.ARM64;
}
if (abi == AgentABI.UNKNOWN) {
log.error("为鸿蒙设备{}准备Agent时发现设备是不支持的CPU架构相关信息{}", hdcDevice.getConnectKey(), archContent);
return false;
}
File agentFile = HarmonyDeviceManager.getAgentFile(abi);
if (agentFile == null) {
log.error("没有准备架构为{}的鸿蒙设备Agent", abi);
return false;
}
try (HDCSession session = Hdc.getInstance().shell(hdcDevice, DELETE_AGENT_FILE_SHELL)) {
log.debug("鸿蒙设备{}删除agent文件输出{}", hdcDevice.getConnectKey(), session.readAllLines());
}
boolean sendFileResult = Hdc.getInstance().sendFile(hdcDevice, agentFile, DEVICE_AGENT_PATH);
if (!sendFileResult) {
log.error("为设备{}传输Agent文件失败", hdcDevice.getConnectKey());
return false;
}
String uiTestContent = "";
try (HDCSession uiTestSession = Hdc.getInstance().shell(hdcDevice, "uitest start-daemon singleness")) {
uiTestContent = uiTestSession.readAllLines();
}
log.info("设备运行uitest启动Agent输出\n{}", uiTestContent);
//再校验一次
try (HDCSession psSession = Hdc.getInstance().shell(hdcDevice, "ps -ef | grep uitest")) {
psContent = psSession.readAllLines();
}
boolean result = psContent.contains("uitest start-daemon singleness");
if (result) {
log.info("鸿蒙设备{}的Agent(uitest)已启动", hdcDevice.getConnectKey());
} else {
log.info("鸿蒙设备{}的Agent(uitest)启动失败,输出:{}", hdcDevice.getConnectKey(), psContent);
}
return result;
}
/**
*
* <br>
* 使
*
* @return
*/
private boolean isForwardExists() {
for (ForwardRule forwardRecord : Hdc.getInstance().listForward()) {
if (forwardRecord.getConnectKey().equals(hdcDevice.getConnectKey())
&& forwardRecord.deviceString().equals("tcp:" + AGENT_PORT)) {
try {
localPort = Integer.parseInt(forwardRecord.getLocalContent());
log.info("鸿蒙设备{}存在对Agent的端口转发{}", hdcDevice.getConnectKey(), forwardRecord.taskString());
return true;
} catch (NumberFormatException ignored) {
}
}
}
return false;
}
/**
*
* <br>
*
*
* @return
*/
private boolean createForward() {
int port = getAvailablePort();
if (port == 0) {
log.error("无法找到可用端口");
return false;
}
ForwardRule forwardRule = new ForwardRule(hdcDevice.getConnectKey(), ForwardRule.ListenScheme.TCP, String.valueOf(AGENT_PORT), ForwardRule.ListenScheme.TCP, String.valueOf(port), false);
boolean createResult = Hdc.getInstance().createForward(forwardRule);
if (createResult) {
localPort = port;
}
return createResult;
}
/**
* Agent
*/
private void cancelForward() {
for (ForwardRule forwardRule : Hdc.getInstance().listForward()) {
if (forwardRule.getConnectKey().equals(hdcDevice.getConnectKey())
&& !forwardRule.isReverse()
&& forwardRule.deviceString().equals("tcp:" + AGENT_PORT)
) {
if (!Hdc.getInstance().removeForwardRule(forwardRule)) {
log.warn("移除鸿蒙设备端口转发失败:{}", forwardRule.taskString());
}
}
}
}
/**
* TCP
*
* @return 0
*/
private int getAvailablePort() {
int port = 0;
try {
ServerSocket serverSocket = new ServerSocket(0);
serverSocket.setReuseAddress(true);
port = serverSocket.getLocalPort();
serverSocket.close();
} catch (IOException e) {
log.error("无法为设备{}的HyppiumAgent获取可用转发端口", hdcDevice.getConnectKey());
}
return port;
}
/**
*
*
* @return
*/
public int getLocalPort() {
return localPort;
}
/**
* Agent Session
*
* @return Sessionnull
*/
public Session createSession() {
try {
return new Session();
} catch (IOException e) {
log.error("为鸿蒙设备{}的Agent创建session失败", hdcDevice.getConnectKey(), e);
}
return null;
}
private boolean doRequest(SessionRequest request, OutputStream outputStream) {
if (request.isTimeout()) {
return false;
}
byte[] sendData = request.sendData;
ByteBuffer buffer = ByteBuffer.allocate(sendData.length + PROTOCOL_BASIC_LENGTH);
buffer.put(PROTOCOL_HEADER).putInt(request.requestCode).putInt(sendData.length).put(sendData).put(PROTOCOL_TAIL);
buffer.position(0);
try {
outputStream.write(buffer.array());
outputStream.flush();
} catch (IOException e) {
log.error("向鸿蒙设备{}发送数据失败", hdcDevice.getConnectKey(), e);
return false;
}
return true;
}
public class Session implements Closeable, Runnable {
private final Socket socket;
private final DataInputStream inputStream;
private final OutputStream outputStream;
private boolean closed = false;
private final Object requestLock = new Object();
private final Thread thread = new Thread(this);
private final ConcurrentHashMap<Integer, SessionRequest> requestMap = new ConcurrentHashMap<>();
private Runnable onCloseListener = null;
private Session() throws IOException {
socket = new Socket("127.0.0.1", localPort);
inputStream = new DataInputStream(socket.getInputStream());
outputStream = socket.getOutputStream();
this.thread.setName("Harmony-Agent-Session-" + hdcDevice.getConnectKey());
this.thread.start();
}
@Override
public void run() {
while (!isClosed() && !thread.isInterrupted() && hdcDevice.getConnectStatus() == HDCConnectStatus.CONNECTED) {
doRead();
}
// log.debug("停止读取数据包");
close();
}
private void doRead() {
//数据包头
// log.debug("开始读取数据包");
try {
byte[] readHeader = InputStreamUtils.readNBytes(inputStream,PROTOCOL_HEADER.length);
if (!Arrays.equals(readHeader, PROTOCOL_HEADER)) {
log.error("读取鸿蒙设备{}的响应头校验失败,期望:{},实际:[{}]{}", hdcDevice.getConnectKey(), new String(PROTOCOL_HEADER), readHeader.length, new String(readHeader));
close();
return;
}
} catch (IOException e) {
if (!isClosed()) {
log.error("读取鸿蒙设备{}的响应头失败", hdcDevice.getConnectKey(), e);
close();
}
return;
}
SessionRequest targetRequest = null;
//请求码
try {
int readRequestCode = inputStream.readInt();
targetRequest = requestMap.get(readRequestCode);
if (targetRequest == null) {
log.warn("读取鸿蒙设备{}的请求时无法找到对应的请求,请求码:{}", hdcDevice.getConnectKey(), readRequestCode);
}
} catch (IOException e) {
if (!isClosed()) {
log.error("读取鸿蒙设备{}的请求码失败", hdcDevice.getConnectKey(), e);
close();
}
return;
}
//数据长度
int dataLength = 0;
try {
dataLength = inputStream.readInt();
if (dataLength <= 0) {
throw new IOException("鸿蒙设备" + hdcDevice.getConnectKey() + "回复的数据长度小于等于0");
}
} catch (IOException e) {
if (!isClosed()) {
log.error("读取鸿蒙设备{}的数据长度失败", hdcDevice.getConnectKey(), e);
close();
}
return;
}
//数据内容
byte[] data = new byte[dataLength];
try {
inputStream.readFully(data);
} catch (IOException e) {
if (!isClosed()) {
log.error("读取鸿蒙设备{}的数据失败", hdcDevice.getConnectKey(), e);
close();
}
return;
}
//数据包尾
try {
byte[] readTail = InputStreamUtils.readNBytes(inputStream,PROTOCOL_TAIL.length);
if (!Arrays.equals(readTail, PROTOCOL_TAIL)) {
log.warn("鸿蒙设备{}数据包尾不匹配,数据可能被截断", hdcDevice.getConnectKey());
close();
return;
}
} catch (IOException e) {
if (!isClosed()) {
log.error("读取鸿蒙设备{}的响应尾失败", hdcDevice.getConnectKey(), e);
close();
}
return;
}
if (targetRequest != null && !targetRequest.isTimeout()) {
targetRequest.response(data);
}
// log.debug("数据包读取处理完毕");
}
public boolean isClosed() {
return closed && socket.isClosed();
}
public void setOnCloseListener(Runnable onCloseListener) {
this.onCloseListener = onCloseListener;
}
private boolean requestAction(RequestData<?> requestData, long timeout, String description) {
if (isClosed()) return false;
boolean result = true;
SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), timeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
try {
result = request(sessionRequest, description);
} catch (CaptureAlreadyRunningException ignored) {
result = false;
}
requestMap.remove(sessionRequest.requestCode);
return result;
}
private boolean requestStartCaptureDataAction(RequestData<?> requestData, long startTimeout, String description, OnCaptureData onCaptureData) throws CaptureAlreadyRunningException {
if (isClosed()) return false;
boolean result = false;
SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), startTimeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
result = request(sessionRequest, description);
if (result) {
sessionRequest.setOnCaptureData(onCaptureData);
}
return result;
}
private boolean requestCancelCaptureDataAction(RequestData<?> requestData, long timeout, String description, OnCaptureData onCaptureData) {
if (isClosed()) return false;
boolean result = false;
SessionRequest sessionRequest = new SessionRequest(requestData.toByteArray(), timeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
try {
result = request(sessionRequest, description);
} catch (CaptureAlreadyRunningException ignored) {
}
int foundRequestCode = 0;
for (Integer key : requestMap.keySet()) {
if (requestMap.get(key).getOnCaptureData() == onCaptureData) {
foundRequestCode = key;
log.debug("找到请求码: {}", foundRequestCode);
log.debug("从map中移除一个数据监听");
break;
}
}
if (foundRequestCode > 0) {
requestMap.remove(foundRequestCode);
}
return result;
}
private boolean request(SessionRequest request, String description) throws CaptureAlreadyRunningException {
if (isClosed()) return false;
boolean result = true;
synchronized (requestLock) {
if (!doRequest(request, outputStream)) {
log.error("对鸿蒙设备{}的Agent请求{}失败。超时状态:{}", hdcDevice.getConnectKey(), description, request.isTimeout());
result = false;
}
}
if (result) {
byte[] responseData = request.waitResponse();
if (responseData == null) {
log.error("未能获取鸿蒙设备{}的{}请求结果。超时状态:{}", hdcDevice.getConnectKey(), description, request.isTimeout());
result = false;
}
if (result) {
String responseJsonText = new String(responseData);
Response response = HarmonyDeviceManager.gson.fromJson(responseJsonText, Response.class);
result = response.isOk();
if (!result) {
log.error("鸿蒙设备{}的{}请求失败,原因:{},原始响应:{}", hdcDevice.getConnectKey(), description, response.exception, responseJsonText);
if (response.exception != null && response.exception.startsWith(DATA_CAPTURE_ALREADY_RUNNING_PREFIX)) {
throw new CaptureAlreadyRunningException(response.exception.substring(DATA_CAPTURE_ALREADY_RUNNING_PREFIX.length()));
}
}
}
}
return result;
}
/**
*
*
* @param timeout
* @return
*/
public boolean pressBack(long timeout) {
return requestAction(PressBack.getInstance(), timeout, "按下返回键");
}
/**
*
*
* @param timeout
* @return
*/
public boolean pressHome(long timeout) {
return requestAction(PressHome.getInstance(), timeout, "按下主屏键");
}
/**
*
* <br>
* Mate60
*
* @param timeout
* @return
*/
public boolean pressRecentApp(long timeout) {
return requestAction(PressRecentApp.getInstance(), timeout, "按下最近App键");
}
/**
*
*
* @param timeout
* @return
*/
public boolean pressPowerKey(long timeout) {
return requestAction(PressPowerKey.getInstance(), timeout, "按下电源键");
}
/**
*
*
* @param timeout
* @return
*/
public boolean pressUpVolume(long timeout) {
return requestAction(PressUpVolume.getInstance(), timeout, "按下音量上键");
}
/**
*
*
* @param timeout
* @return
*/
public boolean pressDownVolume(long timeout) {
return requestAction(PressDownVolume.getInstance(), timeout, "按下音量下键");
}
/**
*
*
* @param scale
* @param timeout
* @return
*/
public boolean pinch(float scale, long timeout) {
return requestAction(Pinch.getInstance(scale), timeout, "双指捏");
}
public boolean touchDown(int x, int y, long timeout) {
return requestAction(new TouchDown(x, y), timeout, "按下坐标");
}
public boolean touchMove(int x, int y, long timeout) {
return requestAction(new TouchMove(x, y), timeout, "移动坐标");
}
public boolean touchUp(int x, int y, long timeout) {
return requestAction(new TouchUp(x, y), timeout, "抬起坐标");
}
/**
*
*
* @param scale 0-1
* @param onCaptureData
* @param timeout
* @return
* @throws CaptureAlreadyRunningException
*/
public boolean startCaptureScreenImageStream(float scale, OnCaptureData onCaptureData, long timeout) throws CaptureAlreadyRunningException {
return requestStartCaptureDataAction(StartCaptureScreenStream.getInstance(scale), timeout, "开始捕捉屏幕图片流", onCaptureData);
}
/**
*
*
* @param onCaptureData
* @param timeout
* @return
*/
public boolean stopCaptureScreenImageStream(OnCaptureData onCaptureData, long timeout) {
return requestCancelCaptureDataAction(StopCaptureScreenStream.getInstance(), timeout, "停止捕获屏幕图片流", onCaptureData);
}
/**
* UI
*
* @param onCaptureData
* @param timeout
* @return
* @throws CaptureAlreadyRunningException
*/
public boolean startCaptureUiAction(OnCaptureData onCaptureData, long timeout) throws CaptureAlreadyRunningException {
return requestStartCaptureDataAction(StartCaptureUiAction.getInstance(), timeout, "开始捕获UI动作", onCaptureData);
}
/**
* UI
*
* @param onCaptureData
* @param timeout
* @return
*/
public boolean stopCaptureUiAction(OnCaptureData onCaptureData, long timeout) {
return requestCancelCaptureDataAction(StopCaptureUiAction.getInstance(), timeout, "停止捕获UI动作", onCaptureData);
}
/**
* UI
*
* @param timeout
* @return UInull
*/
public UiComponent captureLayout(long timeout) {
if (isClosed()) return null;
SessionRequest sessionRequest = new SessionRequest(CaptureLayout.getInstance().toByteArray(), timeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
synchronized (requestLock) {
if (!doRequest(sessionRequest, outputStream)) {
log.error("对鸿蒙设备{}的Agent请求布局数据失败。超时状态{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
requestMap.remove(sessionRequest.requestCode);
return null;
}
}
byte[] responseData = sessionRequest.waitResponse();
requestMap.remove(sessionRequest.requestCode);
if (responseData == null) {
log.error("未能获取鸿蒙设备{}的布局数据。超时状态:{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
return null;
}
String jsonText = new String(responseData).replace(":\"\"", ":null");
CaptureLayoutData captureLayoutData;
try {
captureLayoutData = HarmonyDeviceManager.gson.fromJson(jsonText, CaptureLayoutData.class);
} catch (JsonParseException e) {
log.error("解析鸿蒙设备{}的布局json数据时发生错误", hdcDevice.getConnectKey(), e);
return null;
}
return captureLayoutData.result;
}
/**
*
*
* @param direction 00°190°2180°3270°
* @param timeout
* @return
*/
public boolean setDisplayRotation(int direction, long timeout) {
return requestAction(new RotationDisplay(direction), timeout, "设置屏幕方向:" + direction);
}
/**
*
* <br>
*
* @param timeout
* @return -100°190°2180°3270°
*/
public int getDisplayRotation(long timeout) {
if (isClosed()) return -1;
SessionRequest sessionRequest = new SessionRequest(GetDisplayRotation.getInstance().toByteArray(), timeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
synchronized (requestLock) {
if (!doRequest(sessionRequest, outputStream)) {
log.error("对鸿蒙设备{}的Agent请求屏幕方向数据失败。超时状态{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
requestMap.remove(sessionRequest.requestCode);
return -1;
}
}
byte[] responseData = sessionRequest.waitResponse();
requestMap.remove(sessionRequest.requestCode);
if (responseData == null) {
log.error("未能获取鸿蒙设备的{}的屏幕方向。超时状态:{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
return -1;
}
String jsonText = new String(responseData);
try {
ResultData<Integer> displayRotationData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken<ResultData<Integer>>() {
}.getType());
return displayRotationData.result;
} catch (JsonParseException e) {
log.error("解析鸿蒙设备{}屏幕方向数据时发生JSON解析错误文本{}", hdcDevice.getConnectKey(), jsonText);
}
return -1;
}
public DisplaySize getScreenSize(long timeout) {
if (isClosed()) return null;
SessionRequest sessionRequest = new SessionRequest(GetDisplaySize.getInstance().toByteArray(), timeout);
requestMap.put(sessionRequest.requestCode, sessionRequest);
synchronized (requestLock) {
if (!doRequest(sessionRequest, outputStream)) {
log.error("对鸿蒙设备{}的Agent请求屏幕大小数据失败。超时状态{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
requestMap.remove(sessionRequest.requestCode);
return null;
}
}
byte[] responseData = sessionRequest.waitResponse();
requestMap.remove(sessionRequest.requestCode);
if (responseData == null) {
log.error("未能获取鸿蒙设备的{}的屏幕大小。超时状态:{}", hdcDevice.getConnectKey(), sessionRequest.isTimeout());
return null;
}
String jsonText = new String(responseData);
try {
ResultData<DisplaySize> resultData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken<ResultData<DisplaySize>>() {
}.getType());
return resultData.result;
} catch (JsonParseException e) {
log.error("解析鸿蒙设备{}屏幕大小数据时发生JSON解析错误文本{}", hdcDevice.getConnectKey(), jsonText);
}
return null;
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
requestMap.clear();
try {
socket.close();
} catch (IOException e) {
log.warn("关闭鸿蒙设备{}的一个Agent Session时发生IO错误", hdcDevice.getConnectKey(), e);
}
if (onCloseListener != null) {
try {
onCloseListener.run();
} catch (Exception e) {
log.error("关闭鸿蒙设备{}的一个Agent Session后执行关闭监听回调时发生异常", hdcDevice.getConnectKey());
}
}
}
}
private class SessionRequest {
final int requestCode = requestCodeProvider.next();
final byte[] sendData;
final long createTime = System.currentTimeMillis();
final long timeout;
byte[] responseData = null;
final Object waitResponseLock = new Object();
OnCaptureData onCaptureData = null;
public SessionRequest(byte[] sendData, long timeout) {
this.sendData = sendData;
this.timeout = timeout;
}
public boolean isTimeout() {
return onCaptureData == null && (System.currentTimeMillis() - createTime > timeout);
}
public void setOnCaptureData(OnCaptureData onCaptureData) {
this.onCaptureData = onCaptureData;
}
public OnCaptureData getOnCaptureData() {
return onCaptureData;
}
public void response(byte[] data) {
if (onCaptureData == null) {
responseData = data;
synchronized (waitResponseLock) {
waitResponseLock.notifyAll();
}
} else {
try {
onCaptureData.onCaptureData(data);
} catch (Exception e) {
log.error("处理鸿蒙设备{}的Agent捕获到的数据时发生异常", hdcDevice.getConnectKey(), e);
}
}
}
public byte[] waitResponse() {
synchronized (waitResponseLock) {
if (timeout > 0 && onCaptureData == null) {
try {
waitResponseLock.wait(timeout);
} catch (InterruptedException e) {
return null;
}
} else {
try {
waitResponseLock.wait();
} catch (InterruptedException e) {
return null;
}
}
}
return responseData;
}
}
/**
*
* <br>
* UI
*/
public interface OnCaptureData {
/**
*
* <br>
* Session线
*
* @param data
*/
void onCaptureData(byte[] data);
}
/**
*
* <br>
*
*/
public static class CaptureAlreadyRunningException extends Exception {
public CaptureAlreadyRunningException(String message) {
super(message + "已经正在运行");
}
}
}

View File

@ -0,0 +1,20 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
import java.util.concurrent.atomic.AtomicInteger;
public class RequestCodeProvider {
private static final int START_VALUE = 0x01000000;
/**
* 0x01000000agent.so
*/
private AtomicInteger number = new AtomicInteger(START_VALUE);
public int next() {
int value = number.incrementAndGet();
if (value == Integer.MAX_VALUE) {
number.set(START_VALUE);
}
return value;
}
}

View File

@ -0,0 +1,46 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Hyppium Agent
*/
public abstract class RequestData<T> {
private final Logger log = LoggerFactory.getLogger(RequestData.class);
private final String module = "com.ohos.devicetest.hypiumApiHelper";
private final String method;
private final Params<T> params = new Params<>();
public RequestData(String method, String api, T args) {
this.method = method;
this.params.api = api;
this.params.args = args;
}
public String getModule() {
return module;
}
public String getMethod() {
return method;
}
public byte[] toByteArray() {
String jsonText = HarmonyDeviceManager.gson.toJson(this);
log.debug("RequestData:{}", jsonText);
return jsonText.getBytes();
}
static class Params<T> {
String api;
T args = null;
private Params() {
}
}
}

View File

@ -0,0 +1,10 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
public class Response {
public Boolean result = null;
public String exception = null;
public boolean isOk() {
return (result == null || result) && exception == null;
}
}

View File

@ -0,0 +1,18 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
public abstract class StaticRequestData<T> extends RequestData<T> {
private byte[] data = null;
public StaticRequestData(String method, String api, T args) {
super(method, api, args);
}
@Override
public byte[] toByteArray() {
if (data == null) {
data = super.toByteArray();
}
return data;
}
}

View File

@ -0,0 +1,12 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.RequestData;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.PositionData;
public class AbstractTouch extends RequestData<PositionData> {
public AbstractTouch(String api, int x, int y) {
super("Gestures", api, new PositionData(x, y));
}
}

View File

@ -0,0 +1,22 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
/**
*
*/
public class CaptureLayout extends StaticRequestData<Object> {
private static CaptureLayout instance;
public static CaptureLayout getInstance() {
if (instance == null) {
instance = new CaptureLayout();
}
return instance;
}
private CaptureLayout() {
super("Captures", "captureLayout", new Object());
}
}

View File

@ -0,0 +1,20 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
public class GetDisplayRotation extends StaticRequestData<Object> {
private static GetDisplayRotation instance;
public static GetDisplayRotation getInstance() {
if (instance == null) {
instance = new GetDisplayRotation();
}
return instance;
}
private GetDisplayRotation() {
super("CtrlCmd", "getDisplayRotation", new Object());
}
}

View File

@ -0,0 +1,20 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
public class GetDisplaySize extends StaticRequestData<Object> {
private static GetDisplaySize instance;
public static GetDisplaySize getInstance() {
if (instance == null) {
instance = new GetDisplaySize();
}
return instance;
}
private GetDisplaySize() {
super("CtrlCmd", "getDisplaySize", new Object());
}
}

View File

@ -0,0 +1,18 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.RequestData;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.ScaleData;
/**
*
*/
public class Pinch extends RequestData<ScaleData> {
public static Pinch getInstance(Float scale) {
return new Pinch(scale);
}
private Pinch(Float scale) {
super("Gestures", "pinch", new ScaleData(scale));
}
}

View File

@ -0,0 +1,23 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
/**
*
*/
public class PressBack extends StaticRequestData<Object> {
private static PressBack instance;
public static PressBack getInstance() {
if (instance == null) {
instance = new PressBack();
}
return instance;
}
private PressBack() {
super("Gestures", "pressBack", new Object());
}
}

View File

@ -0,0 +1,19 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
public class PressDownVolume extends StaticRequestData<Object> {
private static PressDownVolume instance;
public static PressDownVolume getInstance() {
if (instance == null) {
instance = new PressDownVolume();
}
return instance;
}
private PressDownVolume() {
super("CtrlCmd", "downVolume", new Object());
}
}

View File

@ -0,0 +1,23 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
/**
*
*/
public class PressHome extends StaticRequestData<Object> {
private static PressHome instance;
public static PressHome getInstance() {
if (instance == null) {
instance = new PressHome();
}
return instance;
}
private PressHome() {
super("Gestures", "pressHome", new Object());
}
}

View File

@ -0,0 +1,19 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
public class PressPowerKey extends StaticRequestData<Object> {
private static PressPowerKey instance;
public static PressPowerKey getInstance() {
if (instance == null) {
instance = new PressPowerKey();
}
return instance;
}
private PressPowerKey() {
super("CtrlCmd", "pressPowerKey", new Object());
}
}

View File

@ -0,0 +1,23 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
/**
* App
*/
public class PressRecentApp extends StaticRequestData<Object> {
private static PressRecentApp instance;
public static PressRecentApp getInstance() {
if (instance == null) {
instance = new PressRecentApp();
}
return instance;
}
private PressRecentApp() {
super("Gestures", "pressRecentApp", new Object());
}
}

View File

@ -0,0 +1,19 @@
package net.northking.cctp.upperComputer.driver.harmony.hyppium.action;
import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData;
public class PressUpVolume extends StaticRequestData<Object> {
private static PressUpVolume instance;
public static PressUpVolume getInstance() {
if (instance == null) {
instance = new PressUpVolume();
}
return instance;
}
private PressUpVolume() {
super("CtrlCmd", "upVolume", new Object());
}
}

Some files were not shown because too many files have changed in this diff Show More