harmony设备接入上位机、投屏,自动化、以及任务、计划等
parent
8beaa4b87a
commit
565c5f1ee9
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package net.northking.cctp.se.util;
|
||||
|
||||
public class HarmonyUtil extends PhoneUtil{
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package net.northking.cctp.scriptcase.tools.hapUtil.resources;
|
||||
|
||||
public class IndexHeader {
|
||||
String version;
|
||||
int fileSize;
|
||||
int limitKeyConfigSize;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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=",">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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); // 在图片的x,y上画上一个直径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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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); // 在图片的x,y上画上一个直径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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
* 因鸿蒙hdc的API并未完全解析完毕,因此仅支持连接本机的设备
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 不同CPU架构的Agent文件Map
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
* 不为null时,标志着当前处于需要开启UI事件捕捉的状态,当设备从不可用恢复时,需要自动重新开始UI事件数据捕捉
|
||||
*/
|
||||
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 获取到的UI层级树数据,null为获取失败
|
||||
*/
|
||||
public UiComponent captureLayout() {
|
||||
return scopeRequest(() -> session.captureLayout(REQUEST_TIMEOUT), UiComponent.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置屏幕方向
|
||||
*
|
||||
* @param direction 屏幕方向值,逆时针:0为0°,1为90°,2为180°,3为270°
|
||||
* @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 屏幕方向值,-1为获取失败,逆时针:0为0°,1为90°,2为180°,3为270°
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记byteBuffer中的数据已经被处理完毕了,新的数据可以进入byteBuffer
|
||||
*/
|
||||
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>
|
||||
* onRead方法将在调用的ThreadScope中运行
|
||||
*/
|
||||
public interface OnReadBufferCallback<IIOJob> {
|
||||
/**
|
||||
* 单词读取操作完成
|
||||
*
|
||||
* @param buffer 读取到的数据buffer,如果为null,则读取失败,检查异常
|
||||
* @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>
|
||||
* 本类逻辑将在指定的ThreadScope执行,并不在IOScope中<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package net.northking.cctp.upperComputer.deviceManager.thread.util;
|
||||
|
||||
/**
|
||||
* 交给线程块运行的代码块<br>
|
||||
* 代码块将在合适的时候执行
|
||||
*/
|
||||
public interface LaunchBlock {
|
||||
void run(ThreadJob job);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步读取InputStream(不要用来读取网络Socket),并在此线程块中处理读取结果<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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
* 借助shell的uitest实现,实际为点击输入框、复制并粘贴指定文本
|
||||
*
|
||||
* @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 截图png文件数据,0长度时为截图失败
|
||||
*/
|
||||
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 截图png文件数据,0长度时为截图失败
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* HDC设备监控,用于监听HDC设备的变化
|
||||
* <br>
|
||||
* 每0.5秒查询一次(遵循DevEco 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设备监听退出................................");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 设备id,如果为null,则不指定设备
|
||||
*/
|
||||
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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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 HDC会话,null为失败
|
||||
*/
|
||||
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 hdc版本,null则为获取失败
|
||||
*/
|
||||
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 true表示成功,false表示失败
|
||||
*/
|
||||
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 -v的输出行转变为HDCDevice对象
|
||||
*
|
||||
* @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 命令行字符串,如果null则进入shell终端交互会话
|
||||
* @return HDCSession,null为通过HDC执行shell动作失败
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package net.northking.cctp.upperComputer.driver.harmony.hyppium;
|
||||
|
||||
public enum AgentABI {
|
||||
ARM64,
|
||||
X86,
|
||||
UNKNOWN
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 通过Hyppium的agent.so控制和获取设备信息
|
||||
* <br>
|
||||
* 鸿蒙后门:通过uitest的隐藏start-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:";
|
||||
|
||||
/**
|
||||
* 杀死uitest的shell
|
||||
*/
|
||||
private static final String KILL_UITEST_SHELL = "killall -9 uitest";
|
||||
|
||||
/**
|
||||
* 删除agent文件的shell
|
||||
*/
|
||||
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 如果成功返回一个Session实例,null则为创建失败
|
||||
*/
|
||||
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 获取到的UI层级树数据,null为获取失败
|
||||
*/
|
||||
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 屏幕方向值,逆时针:0为0°,1为90°,2为180°,3为270°
|
||||
* @param timeout 超时时间
|
||||
* @return 是否操作成功
|
||||
*/
|
||||
public boolean setDisplayRotation(int direction, long timeout) {
|
||||
return requestAction(new RotationDisplay(direction), timeout, "设置屏幕方向:" + direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得屏幕方向
|
||||
* <br>
|
||||
*
|
||||
* @param timeout 超时时间
|
||||
* @return 屏幕方向值,-1为获取失败,逆时针:0为0°,1为90°,2为180°,3为270°
|
||||
*/
|
||||
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 + "已经正在运行");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
/**
|
||||
* 需要从0x01000000开始,小于此数会导致agent.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;
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue