harmony设备接入上位机、投屏,自动化、以及任务、计划等
parent
8beaa4b87a
commit
565c5f1ee9
|
@ -154,8 +154,8 @@ public class DebugServiceImpl implements DebugService {
|
||||||
Object type = request.getData().get("type");
|
Object type = request.getData().get("type");
|
||||||
boolean mobileFlag = false;
|
boolean mobileFlag = false;
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
// 0-接口 1- b/s 2 c/s 3 安卓 4 ios
|
// 0-接口 1- b/s 2 c/s 3 安卓 4 ios 5 harmony
|
||||||
mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + "");
|
mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + "") || "5".equalsIgnoreCase(type+"");
|
||||||
}
|
}
|
||||||
if (mobileFlag && DebugerConst.CommandConst.CMD_DEVICE_INIT.equalsIgnoreCase(request.getCmd())) { //设置应用下载的url
|
if (mobileFlag && DebugerConst.CommandConst.CMD_DEVICE_INIT.equalsIgnoreCase(request.getCmd())) { //设置应用下载的url
|
||||||
Map<String, Object> data = request.getData();
|
Map<String, Object> data = request.getData();
|
||||||
|
|
|
@ -135,6 +135,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
driver = createAndroidDriver(appiumServerUrl, appiumHost + ":8808", deviceInfo.getDeviceId(), null);
|
driver = createAndroidDriver(appiumServerUrl, appiumHost + ":8808", deviceInfo.getDeviceId(), null);
|
||||||
} else if ("1".equalsIgnoreCase(deviceInfo.getPlatform()) || "ios".equalsIgnoreCase(deviceInfo.getPlatform())) {
|
} else if ("1".equalsIgnoreCase(deviceInfo.getPlatform()) || "ios".equalsIgnoreCase(deviceInfo.getPlatform())) {
|
||||||
driver = createIosDriver(appiumServerUrl, deviceInfo, null);
|
driver = createIosDriver(appiumServerUrl, deviceInfo, null);
|
||||||
|
}else if ("2".equalsIgnoreCase(deviceInfo.getPlatform()) || "harmony".equalsIgnoreCase(deviceInfo.getPlatform())) {
|
||||||
|
driver = createHarmonyDriver();
|
||||||
}
|
}
|
||||||
if (driver != null) {
|
if (driver != null) {
|
||||||
log.info("Driver for device[{}] created successfully.", deviceInfo.getDeviceId());
|
log.info("Driver for device[{}] created successfully.", deviceInfo.getDeviceId());
|
||||||
|
@ -145,6 +147,18 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
return driver;
|
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) {
|
private DeviceDriver createIosDriver(URL appiumServerUrl, DeviceInfo deviceInfo, Map<String, String> others) {
|
||||||
MobileAutomationSelectorConfig selectorConfig = SpringUtils.getBean(MobileAutomationSelectorConfig.class);
|
MobileAutomationSelectorConfig selectorConfig = SpringUtils.getBean(MobileAutomationSelectorConfig.class);
|
||||||
log.info("拿到的自动化类型:{}", selectorConfig.getType());
|
log.info("拿到的自动化类型:{}", selectorConfig.getType());
|
||||||
|
@ -501,20 +515,10 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
watchAppActive = new StopWatch();
|
watchAppActive = new StopWatch();
|
||||||
watchAppActive.start();
|
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 {
|
try {
|
||||||
String uuid = (String) driver.getCapabilities().getCapability(UDID);
|
log.debug("设备【{}】开始启动App....................", this.deviceInfo.getDeviceId());
|
||||||
if (driver instanceof AndroidDriver) {
|
PhoneUtil.activeApp(this.deviceInfo.getDeviceId(), appPackage, remoteAddress, deviceInfo.getPlatform());
|
||||||
AndroidUtil.activeApp(uuid, appPackage, remoteAddress);
|
log.debug("设备【{}】完成启动App....................", this.deviceInfo.getDeviceId());
|
||||||
} else if (driver instanceof IOSDriver) {
|
|
||||||
IosUtil.activeApp(uuid, appPackage, remoteAddress);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (isRecord && null != watchAppActive) {
|
if (isRecord && null != watchAppActive) {
|
||||||
watchAppActive.stop();
|
watchAppActive.stop();
|
||||||
|
@ -525,8 +529,6 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pressHome() {
|
private void pressHome() {
|
||||||
AndroidDriver<WebElement> driver = (AndroidDriver<WebElement>) this.deviceDriver;
|
AndroidDriver<WebElement> driver = (AndroidDriver<WebElement>) this.deviceDriver;
|
||||||
try {
|
try {
|
||||||
|
@ -576,8 +578,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
removeApp = driver.removeApp(appPackage);
|
removeApp = driver.removeApp(appPackage);
|
||||||
}
|
}
|
||||||
return removeApp;
|
return removeApp;
|
||||||
} else { //ios
|
} else { //ios和harmony
|
||||||
return IosUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage);
|
return PhoneUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,8 +606,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
appInstalled = driver.isAppInstalled(appPackage);
|
appInstalled = driver.isAppInstalled(appPackage);
|
||||||
}
|
}
|
||||||
return appInstalled;
|
return appInstalled;
|
||||||
} else { //ios
|
} else {
|
||||||
return IosUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage);
|
return PhoneUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,10 +623,9 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
log.warn("启动app时前停止。。。。。。。。。。");
|
log.warn("启动app时前停止。。。。。。。。。。");
|
||||||
throw new ExecuteException("执行停止");
|
throw new ExecuteException("执行停止");
|
||||||
}
|
}
|
||||||
if ("0".equals(deviceInfo.getPlatform())) {
|
boolean success = PhoneUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage, deviceInfo.getPlatform());
|
||||||
return AndroidUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage);
|
log.debug("设备【{}】清除数据结果:{}",deviceId,success);
|
||||||
}
|
return success;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -673,31 +674,11 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
throw new ExecuteException("执行停止");
|
throw new ExecuteException("执行停止");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
boolean isOk = false;
|
boolean success = 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);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (isAndroid) {
|
success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform());
|
||||||
isOk = AndroidUtil.installApp(this.remoteAddress, deviceId, appPath, platform, packageName);
|
|
||||||
} else if (isIOS) {
|
|
||||||
isOk = IosUtil.installApp(this.remoteAddress, deviceId, appPath, platform);
|
|
||||||
} else {
|
|
||||||
log.warn("driver类型未知,安装应用失败!");
|
|
||||||
}
|
|
||||||
long t3 = System.currentTimeMillis();
|
long t3 = System.currentTimeMillis();
|
||||||
log.debug("单独安装应用耗时:{}", (t3 - t2) / 1000);
|
log.debug("单独安装应用耗时:{},结果:{}", (t3 - t2) / 1000,success);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("设备【{}】安装应用【{}】出现异常,重试一次。。。", deviceId, packageName, e);
|
log.warn("设备【{}】安装应用【{}】出现异常,重试一次。。。", deviceId, packageName, e);
|
||||||
try {
|
try {
|
||||||
|
@ -717,15 +698,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
t2 = System.currentTimeMillis();
|
t2 = System.currentTimeMillis();
|
||||||
log.debug("单独下载应用耗时:{}", (t2 - t1) / 1000);
|
log.debug("单独下载应用耗时:{}", (t2 - t1) / 1000);
|
||||||
log.info("download app finish,start install ............");
|
log.info("download app finish,start install ............");
|
||||||
if (isAndroid) {
|
success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform());
|
||||||
// 回到桌面
|
|
||||||
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类型未知,安装应用失败!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if (this.deviceDriver instanceof AndroidDriver) {
|
// if (this.deviceDriver instanceof AndroidDriver) {
|
||||||
// AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) this.deviceDriver;
|
// AppiumDriver<WebElement> driver = (AppiumDriver<WebElement>) this.deviceDriver;
|
||||||
|
@ -770,11 +743,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startRecord() {
|
public void startRecord() {
|
||||||
if ("0".equals(deviceInfo.getPlatform())) {
|
PhoneUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"),deviceInfo.getOtherInfo().get("tenantId"),deviceInfo.getResolution(),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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -786,11 +755,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
if (ObjectUtil.isNull(initData.get("save"))) {
|
if (ObjectUtil.isNull(initData.get("save"))) {
|
||||||
throw new RuntimeException("视频保存标识不能为空");
|
throw new RuntimeException("视频保存标识不能为空");
|
||||||
}
|
}
|
||||||
if ("0".equals(deviceInfo.getPlatform())) {
|
result = PhoneUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData,deviceInfo.getPlatform());
|
||||||
result = AndroidUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData);
|
log.debug("任务{},得到录频保存地址:{}",initData.get("taskId"),result);
|
||||||
} else {
|
|
||||||
result = IosUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData);
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,7 +828,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
if (this.deviceDriver instanceof MobileDriver) {
|
if (this.deviceDriver instanceof MobileDriver) {
|
||||||
log.debug("设备【{}】采取自研自动化重新启动App....................",this.deviceInfo.getDeviceId());
|
log.debug("设备【{}】采取自研自动化重新启动App....................",this.deviceInfo.getDeviceId());
|
||||||
String uuid = this.deviceInfo.getDeviceId();
|
String uuid = this.deviceInfo.getDeviceId();
|
||||||
IosUtil.terminateApp(uuid, appPackage, remoteAddress);
|
PhoneUtil.terminateApp(uuid, appPackage, remoteAddress,deviceInfo.getPlatform());
|
||||||
} else {
|
} else {
|
||||||
log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId());
|
log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId());
|
||||||
AppiumDriver deviceDriver = (AppiumDriver) this.deviceDriver;
|
AppiumDriver deviceDriver = (AppiumDriver) this.deviceDriver;
|
||||||
|
@ -881,7 +847,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection {
|
||||||
}
|
}
|
||||||
} else if (deviceDriver instanceof IOSDriver) {
|
} else if (deviceDriver instanceof IOSDriver) {
|
||||||
String uuid = (String) deviceDriver.getCapabilities().getCapability(UDID);
|
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);
|
ResponseEntity<Map> resEntity = restTemplate.getForEntity(url, Map.class);
|
||||||
Map resultBody = resEntity.getBody();
|
Map resultBody = resEntity.getBody();
|
||||||
DeviceInfo deviceInfo = new DeviceInfo();
|
DeviceInfo deviceInfo = new DeviceInfo();
|
||||||
|
log.debug("查询得到的设备信息:{}", JSON.toJSONString(resultBody));
|
||||||
if (resultBody != null && (boolean)resultBody.get("success") && resultBody.get("data") != null) { // 请求成功,获取到设备信息
|
if (resultBody != null && (boolean)resultBody.get("success") && resultBody.get("data") != null) { // 请求成功,获取到设备信息
|
||||||
Map data = (Map) resultBody.get("data");
|
Map data = (Map) resultBody.get("data");
|
||||||
log.info("设备【token-{}】请求结果:{}", deviceToken, JsonUtils.toJson(data));
|
log.info("设备【token-{}】请求结果:{}", deviceToken, JsonUtils.toJson(data));
|
||||||
|
@ -64,6 +65,8 @@ public class DeviceConnectionManager implements DeviceConnectionService {
|
||||||
Object forwardPort = deviceMap.get("forwardPort");
|
Object forwardPort = deviceMap.get("forwardPort");
|
||||||
log.debug("拿到设备【{}】在上位机用于adb转发的端口:{}",deviceInfo.getDeviceId(),forwardPort);
|
log.debug("拿到设备【{}】在上位机用于adb转发的端口:{}",deviceInfo.getDeviceId(),forwardPort);
|
||||||
deviceInfo.setForwardPort(Integer.parseInt(forwardPort+""));
|
deviceInfo.setForwardPort(Integer.parseInt(forwardPort+""));
|
||||||
|
}else if ("2".equalsIgnoreCase(deviceInfo.getPlatform())) { //harmony
|
||||||
|
log.debug("该设备【{}】是harmony设备",deviceInfo.getDeviceId());
|
||||||
}
|
}
|
||||||
} else { // pc设备
|
} else { // pc设备
|
||||||
deviceInfo.setMobile(false);
|
deviceInfo.setMobile(false);
|
||||||
|
|
|
@ -10,19 +10,18 @@ import org.springframework.http.HttpEntity;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class AndroidUtil {
|
public class AndroidUtil extends PhoneUtil{
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AndroidUtil.class);
|
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 GET_PACKAGE_VERSION_URL = "http://%s/engine/getPackageVersion";
|
||||||
private static final String ALL_SCREEN_SHOT_URL = "http://%s/engine/uploadShotAllScreen";
|
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 START_RECORD_URL = "http://%s/engine/startRecord";
|
||||||
private static final String END_RECORD_URL = "http://%s/engine/endRecord";
|
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";
|
private static final String RELEASE_ADB_FORWARD_PORT = "http://%s/engine/releaseAdbForwardPort";
|
||||||
|
|
||||||
|
|
||||||
public static boolean releaseAdbForwardPort(String remoteAddress,String deviceId){
|
public static boolean releaseAdbForwardPort(String remoteAddress,String deviceId){
|
||||||
try {
|
try {
|
||||||
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
|
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) {
|
public static String snapshotAllScreen(String remoteAddress, DeviceInfo deviceInfo, String tenantId) {
|
||||||
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) {
|
|
||||||
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
|
DebuggerDeviceInfo info = new DebuggerDeviceInfo();
|
||||||
info.setPlatform(deviceInfo.getPlatform());
|
info.setPlatform(deviceInfo.getPlatform());
|
||||||
info.setDeviceId(deviceInfo.getDeviceId());
|
info.setDeviceId(deviceInfo.getDeviceId());
|
||||||
info.setResolution(deviceInfo.getResolution());
|
info.setResolution(deviceInfo.getResolution());
|
||||||
info.setTenantId(tenantId); //租户id
|
info.setTenantId(tenantId); //租户id
|
||||||
info.setTaskId(taskId);
|
|
||||||
logger.info("参数:{}", JSON.toJSONString(info));
|
logger.info("参数:{}", JSON.toJSONString(info));
|
||||||
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
|
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
|
||||||
String result = null;
|
String result = null;
|
||||||
|
@ -127,44 +73,4 @@ public class AndroidUtil {
|
||||||
return result;
|
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.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
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.element.core.exception.ExecuteException;
|
||||||
import net.northking.cctp.se.device.bean.DebuggerDeviceInfo;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class IosUtil {
|
public class IosUtil extends PhoneUtil{
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(IosUtil.class);
|
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 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();
|
public static Double getWindowsSize(AppiumDriver<WebElement> driver) {
|
||||||
info.setDeviceId(deviceId);
|
if (null == driver) {
|
||||||
info.setAppPackage(appPackage);
|
logger.error("driver获取为空");
|
||||||
HttpEntity<DebuggerDeviceInfo> entity = new HttpEntity<>(info);
|
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 {
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error("设备【{}】查询app是否安装出错", deviceId, e);
|
logger.error("获取手机【{}】宽高比例失败:{}",deviceId, e);
|
||||||
try {
|
throw new ExecuteException("获取手机宽高比例失败");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
private AtuPlanScriptResultDto queryScriptData(List<String> scriptList,List<AtuPlanScriptLinkDto> result) {
|
||||||
AtuPlanScriptResultDto resultDto = new AtuPlanScriptResultDto();
|
AtuPlanScriptResultDto resultDto = new AtuPlanScriptResultDto();
|
||||||
ResultWrapper<AtuScriptInfoResultDto> wrapper = scriptCaseFeignClient.checkScriptData(scriptList);
|
ResultWrapper<AtuScriptInfoResultDto> wrapper = scriptCaseFeignClient.checkScriptData(scriptList);
|
||||||
|
logger.debug("拿到的脚本数据:{}",JSON.toJSONString(wrapper.getData()));
|
||||||
//脚本名称
|
//脚本名称
|
||||||
Map<String, String> scriptMap = wrapper.getData().getScriptMap();
|
Map<String, String> scriptMap = wrapper.getData().getScriptMap();
|
||||||
//校验结果
|
//校验结果
|
||||||
|
@ -3133,7 +3134,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
|
||||||
List<EntrustDeviceDto> mobileTypeList = mobDeviceList.stream()
|
List<EntrustDeviceDto> mobileTypeList = mobDeviceList.stream()
|
||||||
.filter(entrustDeviceDto -> deviceList.contains(entrustDeviceDto.getDeviceId()))
|
.filter(entrustDeviceDto -> deviceList.contains(entrustDeviceDto.getDeviceId()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
logger.debug("设备数量为:{}", mobileTypeList.size());
|
logger.debug("移动端平台:{},委托的设备为:{}", type,JSON.toJSONString(mobileTypeList));
|
||||||
sendEntrustMsgToQueue(entrustMsg, engineInfoListDto.getEngineInfoList(),
|
sendEntrustMsgToQueue(entrustMsg, engineInfoListDto.getEngineInfoList(),
|
||||||
PlanConstant.ENGINE_TYPE_MOBILE, queueName, mobileTypeList);
|
PlanConstant.ENGINE_TYPE_MOBILE, queueName, mobileTypeList);
|
||||||
});
|
});
|
||||||
|
@ -3252,6 +3253,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
|
||||||
|
|
||||||
boolean androidDevice = false;
|
boolean androidDevice = false;
|
||||||
boolean iosDevice = false;
|
boolean iosDevice = false;
|
||||||
|
boolean harmonyDevice = false;
|
||||||
boolean pcDevice = false;
|
boolean pcDevice = false;
|
||||||
for (DeviceList device : feignDto.getDeviceLists()) {
|
for (DeviceList device : feignDto.getDeviceLists()) {
|
||||||
if (PlanConstant.DEVICE_TYPE_PC.equals(device.getDeviceType())){
|
if (PlanConstant.DEVICE_TYPE_PC.equals(device.getDeviceType())){
|
||||||
|
@ -3265,6 +3267,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
|
||||||
case PlanConstant.PLATFORM_TYPE_IOS:
|
case PlanConstant.PLATFORM_TYPE_IOS:
|
||||||
iosDevice = true;
|
iosDevice = true;
|
||||||
break;
|
break;
|
||||||
|
case PlanConstant.PLATFORM_TYPE_HARMONY:
|
||||||
|
harmonyDevice = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3273,6 +3278,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
|
||||||
//用例类型
|
//用例类型
|
||||||
boolean hasAndroid = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID);
|
boolean hasAndroid = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID);
|
||||||
boolean hasIOS = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_IOS);
|
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 hasPC = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_PC);
|
||||||
boolean hasMob = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_MOB);
|
boolean hasMob = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_MOB);
|
||||||
StringBuilder deviceType = new StringBuilder();
|
StringBuilder deviceType = new StringBuilder();
|
||||||
|
@ -3284,6 +3290,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService<AtuPlanInfo>
|
||||||
if (hasIOS && !iosDevice) {
|
if (hasIOS && !iosDevice) {
|
||||||
deviceType.append(MsgConstant.ERROR_PLATFORM_TYPE_IOS);
|
deviceType.append(MsgConstant.ERROR_PLATFORM_TYPE_IOS);
|
||||||
}
|
}
|
||||||
|
if (hasHarmony && !harmonyDevice) {
|
||||||
|
deviceType.append("【Harmony】");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//PC端的校验
|
//PC端的校验
|
||||||
if (hasPC && !pcDevice) {
|
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_IOS= "hasIOS";
|
||||||
public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID= "hasAndroid";
|
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_PC= "hasPC";
|
||||||
public static final String PLATFORM_TYPE_HAS_MOB= "hasMob";
|
public static final String PLATFORM_TYPE_HAS_MOB= "hasMob";
|
||||||
public static final String MAP_JSON_APPALIVE= "appAlive";
|
public static final String MAP_JSON_APPALIVE= "appAlive";
|
||||||
|
|
|
@ -33,6 +33,17 @@ public class AtuPlanScriptResultDto {
|
||||||
@ApiModelProperty("是否存在接口脚本")
|
@ApiModelProperty("是否存在接口脚本")
|
||||||
private boolean hasAPI;
|
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() {
|
public List<AtuPlanScriptLinkDto> getSimpleList() {
|
||||||
return simpleList;
|
return simpleList;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ public class AtuScriptInfoResultDto {
|
||||||
private boolean hasAPI = false;
|
private boolean hasAPI = false;
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否存在harmony脚本")
|
||||||
|
private boolean hasHarmony = false;
|
||||||
|
;
|
||||||
|
|
||||||
@ApiModelProperty("脚本Map")
|
@ApiModelProperty("脚本Map")
|
||||||
private Map<String, String> scriptMap;
|
private Map<String, String> scriptMap;
|
||||||
|
|
||||||
|
@ -97,4 +101,12 @@ public class AtuScriptInfoResultDto {
|
||||||
public void setHasAPI(boolean hasAPI) {
|
public void setHasAPI(boolean hasAPI) {
|
||||||
this.hasAPI = 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 {
|
try {
|
||||||
apkFile = new ApkFile(file);
|
apkFile = new ApkFile(file);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(ScriptCaseAppError.APK_READ_ERR);
|
throw new PlatformRuntimeException(ScriptCaseAppError.APK_READ_ERR);
|
||||||
}
|
}
|
||||||
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
|
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
|
||||||
|
@ -77,7 +77,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
apkMeta = apkFile.getApkMeta();
|
apkMeta = apkFile.getApkMeta();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
||||||
}
|
}
|
||||||
result.put(ScriptConstant.APP_NAME, apkMeta.getName());
|
result.put(ScriptConstant.APP_NAME, apkMeta.getName());
|
||||||
|
@ -87,7 +87,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
|
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
|
||||||
}
|
}
|
||||||
//封装activity
|
//封装activity
|
||||||
|
@ -95,7 +95,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
activity = getActtivity(apkFile);
|
activity = getActtivity(apkFile);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
||||||
}
|
}
|
||||||
result.put(ScriptConstant.ACTIVITY, activity);
|
result.put(ScriptConstant.ACTIVITY, activity);
|
||||||
|
@ -113,7 +113,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
fileMessage.setMessage(result);
|
fileMessage.setMessage(result);
|
||||||
fileMessage.setManifest(manifest);
|
fileMessage.setManifest(manifest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL);
|
throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
@ -121,7 +121,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
apkFile.close();
|
apkFile.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -132,9 +132,9 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
long t1 = System.currentTimeMillis();
|
long t1 = System.currentTimeMillis();
|
||||||
ServletOutputStream out = response.getOutputStream();
|
ServletOutputStream out = response.getOutputStream();
|
||||||
simpleStorageService.getFileAndDirectResponseToHttp(out,result[1]);
|
simpleStorageService.getFileAndDirectResponseToHttp(out, result[1]);
|
||||||
long t2 = System.currentTimeMillis();
|
long t2 = System.currentTimeMillis();
|
||||||
logger.debug("单独从服务器下载应用文件耗时:{}",(t2-t1)/1000);
|
logger.debug("单独从服务器下载应用文件耗时:{}", (t2 - t1) / 1000);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("文件下载流异常", e);
|
logger.error("文件下载流异常", e);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
long t1 = System.currentTimeMillis();
|
long t1 = System.currentTimeMillis();
|
||||||
downLoadAndReturn(response, result);
|
downLoadAndReturn(response, result);
|
||||||
long t2 = System.currentTimeMillis();
|
long t2 = System.currentTimeMillis();
|
||||||
logger.debug("下载应用文件完整耗时:{}",(t2-t1)/1000);
|
logger.debug("下载应用文件完整耗时:{}", (t2 - t1) / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
requestUrl = new URL(urls[i]);
|
requestUrl = new URL(urls[i]);
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
urlCon = (HttpURLConnection) requestUrl.openConnection();
|
urlCon = (HttpURLConnection) requestUrl.openConnection();
|
||||||
|
@ -219,7 +219,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
inputStreams[i] = urlCon.getInputStream();
|
inputStreams[i] = urlCon.getInputStream();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (urlCon == null) {
|
if (urlCon == null) {
|
||||||
urlCon.disconnect();
|
urlCon.disconnect();
|
||||||
|
@ -278,7 +278,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
// 解析应用
|
// 解析应用
|
||||||
ApkFile apkFile = null;
|
ApkFile apkFile = null;
|
||||||
File file=null;
|
File file = null;
|
||||||
try {
|
try {
|
||||||
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
||||||
file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
|
file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
|
||||||
|
@ -290,7 +290,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
apkFile = new ApkFile(file);
|
apkFile = new ApkFile(file);
|
||||||
} catch (IOException e) {
|
} 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);
|
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
||||||
}
|
}
|
||||||
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
|
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
|
||||||
|
@ -298,7 +298,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
apkMeta = apkFile.getApkMeta();
|
apkMeta = apkFile.getApkMeta();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.info("apkFile.getApkMeta()解析APK异常信息:{}",e.getMessage());
|
logger.info("apkFile.getApkMeta()解析APK异常信息:{}", e.getMessage());
|
||||||
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
||||||
}
|
}
|
||||||
result.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
|
result.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
|
||||||
|
@ -310,7 +310,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
|
result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
|
||||||
}
|
}
|
||||||
// 封装activity
|
// 封装activity
|
||||||
|
@ -318,7 +318,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
try {
|
try {
|
||||||
activity = getActtivity(apkFile);
|
activity = getActtivity(apkFile);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.info("getActtivity(apkFile)解析APK异常信息:{}",e.getMessage());
|
logger.info("getActtivity(apkFile)解析APK异常信息:{}", e.getMessage());
|
||||||
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
|
||||||
}
|
}
|
||||||
result.put(ScriptConstant.ACTIVITY, activity);
|
result.put(ScriptConstant.ACTIVITY, activity);
|
||||||
|
@ -340,20 +340,20 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
} catch (PlatformRuntimeException e) {
|
} catch (PlatformRuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (FileDownloadException e) {
|
} catch (FileDownloadException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if (null != apkFile) {
|
if (null != apkFile) {
|
||||||
apkFile.close();
|
apkFile.close();
|
||||||
}
|
}
|
||||||
if(file!=null){
|
if (file != null) {
|
||||||
boolean delete = file.delete();
|
boolean delete = file.delete();
|
||||||
if (!delete) {
|
if (!delete) {
|
||||||
logger.error("删除文件失败");
|
logger.error("删除文件失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fileMessage;
|
return fileMessage;
|
||||||
|
@ -364,7 +364,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
FileMessage fileMessage = new FileMessage();
|
FileMessage fileMessage = new FileMessage();
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
long size = 0;
|
long size = 0;
|
||||||
File file =null;
|
File file = null;
|
||||||
//解析应用
|
//解析应用
|
||||||
try {
|
try {
|
||||||
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
||||||
|
@ -378,7 +378,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
} catch (PlatformRuntimeException e) {
|
} catch (PlatformRuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL);
|
throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL);
|
||||||
}
|
}
|
||||||
if (map.get(ScriptConstant.STATUS_ERR) != null) {
|
if (map.get(ScriptConstant.STATUS_ERR) != null) {
|
||||||
|
@ -399,8 +399,8 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
if (!delete) {
|
if (!delete) {
|
||||||
logger.error("删除文件失败");
|
logger.error("删除文件失败");
|
||||||
}
|
}
|
||||||
}catch (Exception e){
|
} catch (Exception e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
}
|
}
|
||||||
return fileMessage;
|
return fileMessage;
|
||||||
}
|
}
|
||||||
|
@ -409,12 +409,7 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
public FileMessage uploadHarmonyOSLargeApp(String appUrl) {
|
public FileMessage uploadHarmonyOSLargeApp(String appUrl) {
|
||||||
FileMessage fileMessage = new FileMessage();
|
FileMessage fileMessage = new FileMessage();
|
||||||
long size = 0;
|
long size = 0;
|
||||||
File file=null;
|
File file = null;
|
||||||
InputStream inputStream = null;
|
|
||||||
ZipInputStream zin = null;
|
|
||||||
ZipFile zf = null;
|
|
||||||
FileInputStream fileInputStream = null;
|
|
||||||
File apkFile = null;
|
|
||||||
//解析应用
|
//解析应用
|
||||||
try {
|
try {
|
||||||
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
String[] fileNames = MinioPathUtils.idToPath(appUrl);
|
||||||
|
@ -424,107 +419,45 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
logger.info("hap文件为空");
|
logger.info("hap文件为空");
|
||||||
throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
|
throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
|
||||||
}
|
}
|
||||||
//apk文件流
|
Hap hap = new Hap(file);
|
||||||
InputStream apkIn = null;
|
InputStream appIconInputStream = hap.getAppIcon(); //应用图片
|
||||||
//apk文件名
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
String apkFileName = null;
|
byte[] data = new byte[1024];
|
||||||
//hapConfig
|
int length = 0;
|
||||||
String hapConfig = "";
|
while ((length = appIconInputStream.read(data, 0, data.length)) > 0) {
|
||||||
|
byteArrayOutputStream.write(data, 0, length);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
//设置编码
|
fileMessage.setMessage(result);
|
||||||
Charset charset = Charset.forName(ScriptConstant.CHARSET_FOR_NAME);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// hap包放进输入流
|
|
||||||
fileInputStream = new FileInputStream(file);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
logger.error("",e);
|
|
||||||
}
|
|
||||||
inputStream = new BufferedInputStream(fileInputStream);
|
|
||||||
// 压缩包输入流
|
|
||||||
zin = new ZipInputStream(inputStream, charset);
|
|
||||||
// 压缩包文件
|
|
||||||
zf = new ZipFile(file);
|
|
||||||
|
|
||||||
//获取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);
|
|
||||||
|
|
||||||
// 将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.setFileSaveUrl(appUrl);
|
||||||
fileMessage.setFileUrl(appUrl);
|
fileMessage.setFileUrl(appUrl);
|
||||||
fileMessage.setMessage(message);
|
|
||||||
|
|
||||||
} catch (PlatformRuntimeException e) {
|
} catch (PlatformRuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("",e);
|
logger.error("", e);
|
||||||
throw new PlatformRuntimeException(ScriptCaseAppError.HAP_ERR);
|
throw new PlatformRuntimeException(new ErrorMessage("hap解析失败"));
|
||||||
}finally {
|
} 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("删除文件失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
boolean delete = file.delete();
|
boolean delete = file.delete();
|
||||||
if (!delete) {
|
if (!delete) {
|
||||||
|
@ -534,4 +467,59 @@ public class FileMinioServiceImpl implements FileService {
|
||||||
}
|
}
|
||||||
return fileMessage;
|
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);
|
AtuScriptInfoResultDto checkDto = checkScriptType(resultQuery);
|
||||||
map.put(ScriptConstant.HAS_AND,checkDto.isHasAndroid());
|
map.put(ScriptConstant.HAS_AND,checkDto.isHasAndroid());
|
||||||
map.put(ScriptConstant.HAS_IOS,checkDto.isHasIOS());
|
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_PC,checkDto.isHasPC());
|
||||||
map.put(ScriptConstant.HAS_MOB,checkDto.isHasMob());
|
map.put(ScriptConstant.HAS_MOB,checkDto.isHasMob());
|
||||||
|
|
||||||
|
@ -2182,6 +2183,8 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
|
||||||
boolean hasAndroidScript = false;
|
boolean hasAndroidScript = false;
|
||||||
//是否含有IOS用例
|
//是否含有IOS用例
|
||||||
boolean hasIOSScript = false;
|
boolean hasIOSScript = false;
|
||||||
|
//是否含有harmony用例
|
||||||
|
boolean hasHarmonyScript = false;
|
||||||
//是否含有B/S用例
|
//是否含有B/S用例
|
||||||
boolean hasBSScript = false;
|
boolean hasBSScript = false;
|
||||||
//是否含有C/S用例
|
//是否含有C/S用例
|
||||||
|
@ -2211,6 +2214,10 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
|
||||||
hasMobScript = true;
|
hasMobScript = true;
|
||||||
hasIOSScript = true;
|
hasIOSScript = true;
|
||||||
break;
|
break;
|
||||||
|
case ScriptConstant.STR_SIX:
|
||||||
|
hasMobScript = true;
|
||||||
|
hasHarmonyScript = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2222,6 +2229,7 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService<AtuScriptI
|
||||||
dto.setHasAPI(hasAPIScript);
|
dto.setHasAPI(hasAPIScript);
|
||||||
dto.setHasBS(hasBSScript);
|
dto.setHasBS(hasBSScript);
|
||||||
dto.setHasCS(hasCSScript);
|
dto.setHasCS(hasCSScript);
|
||||||
|
dto.setHasHarmony(hasHarmonyScript);
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,7 @@ public class ScriptConstant {
|
||||||
public static final String CASE_NUM = "caseNum";
|
public static final String CASE_NUM = "caseNum";
|
||||||
public static final String HAS_AND = "hasAndroid";
|
public static final String HAS_AND = "hasAndroid";
|
||||||
public static final String HAS_IOS = "hasIOS";
|
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_PC = "hasPC";
|
||||||
public static final String HAS_MOB = "hasMob";
|
public static final String HAS_MOB = "hasMob";
|
||||||
public static final String APP_ALIVE = "appAlive";
|
public static final String APP_ALIVE = "appAlive";
|
||||||
|
|
|
@ -31,6 +31,10 @@ public class AtuScriptInfoResultDto {
|
||||||
private boolean hasAPI = false;
|
private boolean hasAPI = false;
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否存在Harmony脚本")
|
||||||
|
private boolean hasHarmony = false;
|
||||||
|
;
|
||||||
|
|
||||||
@ApiModelProperty("脚本Map")
|
@ApiModelProperty("脚本Map")
|
||||||
private Map<String, String> scriptMap;
|
private Map<String, String> scriptMap;
|
||||||
|
|
||||||
|
@ -95,6 +99,15 @@ public class AtuScriptInfoResultDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHasAPI(boolean hasAPI) {
|
public void setHasAPI(boolean hasAPI) {
|
||||||
|
|
||||||
this.hasAPI = 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
|
from
|
||||||
<include refid="Table_Name"/>
|
<include refid="Table_Name"/>
|
||||||
where
|
where
|
||||||
script_type in ('3','4')
|
script_type in ('3','4','6')
|
||||||
<if test="scriptIds!=null and scriptIds.size>0">
|
<if test="scriptIds!=null and scriptIds.size>0">
|
||||||
and id IN
|
and id IN
|
||||||
<foreach collection="scriptIds" open="(" index="index" item="item" close=")" separator=",">
|
<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.entity.CmdAutomationRequest;
|
||||||
import net.northking.cctp.upperComputer.automation.handler.AndroidAutomationHandler;
|
import net.northking.cctp.upperComputer.automation.handler.AndroidAutomationHandler;
|
||||||
import net.northking.cctp.upperComputer.automation.handler.AutomationMessageHandler;
|
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.automation.handler.IosAutomationHandler;
|
||||||
import net.northking.cctp.upperComputer.deviceManager.AndroidDeviceManager;
|
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.IOSDeviceManager;
|
||||||
import net.northking.cctp.upperComputer.deviceManager.common.Python3;
|
import net.northking.cctp.upperComputer.deviceManager.common.Python3;
|
||||||
import net.northking.cctp.upperComputer.driver.adb.AdbDevice;
|
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.entity.PhoneEntity;
|
||||||
import net.northking.cctp.upperComputer.service.DeviceConnectionService;
|
import net.northking.cctp.upperComputer.service.DeviceConnectionService;
|
||||||
import net.northking.cctp.upperComputer.utils.SpringUtils;
|
import net.northking.cctp.upperComputer.utils.SpringUtils;
|
||||||
|
@ -72,6 +76,11 @@ public class AutomationWebSocketServer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info("收到建立手机【{}】的自动化连接", serial);
|
logger.info("收到建立手机【{}】的自动化连接", serial);
|
||||||
|
boolean deviceIsOnline = checkMobileIsOnline(serial, type);
|
||||||
|
if (!deviceIsOnline) {
|
||||||
|
closeSession(session, "当前设备已经离线,无法创建自动化连接");
|
||||||
|
return;
|
||||||
|
}
|
||||||
boolean success = prepareEnvironment(serial,type,session);
|
boolean success = prepareEnvironment(serial,type,session);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
closeSession(session, "设备自动化连接建立失败,请检查设备是否正常");
|
closeSession(session, "设备自动化连接建立失败,请检查设备是否正常");
|
||||||
|
@ -89,6 +98,8 @@ public class AutomationWebSocketServer {
|
||||||
return AndroidDeviceManager.getInstance().checkMobileIsOnline(serial);
|
return AndroidDeviceManager.getInstance().checkMobileIsOnline(serial);
|
||||||
} else if ("ios".equals(type)){
|
} else if ("ios".equals(type)){
|
||||||
return IOSDeviceManager.getInstance().checkMobileIsOnline(serial);
|
return IOSDeviceManager.getInstance().checkMobileIsOnline(serial);
|
||||||
|
}else if ("harmony".equals(type)){
|
||||||
|
return HarmonyDeviceManager.getInstance().checkMobileIsOnline(serial);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -186,6 +197,15 @@ public class AutomationWebSocketServer {
|
||||||
this.handler = new IosAutomationHandler(phoneEntity,session);
|
this.handler = new IosAutomationHandler(phoneEntity,session);
|
||||||
logger.info("手机[{}]初始化环境完成。。。。。。。", serial);
|
logger.info("手机[{}]初始化环境完成。。。。。。。", serial);
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ package net.northking.cctp.upperComputer.automation.constants;
|
||||||
* @date : 2024/5/13 16:48
|
* @date : 2024/5/13 16:48
|
||||||
*/
|
*/
|
||||||
public enum Command {
|
public enum Command {
|
||||||
CLEAR("清空文本",20);
|
CLEAR("清空文本",20),
|
||||||
|
CLICK("点击", 21);
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private int code;
|
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;
|
package net.northking.cctp.upperComputer.automation.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import net.northking.cctp.upperComputer.automation.constants.AutomationRequestCmd;
|
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.CmdAutomationRequest;
|
||||||
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
import javax.websocket.Session;
|
import javax.websocket.Session;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public abstract class AbstractAutomationHandler implements AutomationMessageHandler{
|
public abstract class AbstractAutomationHandler implements AutomationMessageHandler{
|
||||||
|
@ -19,6 +42,16 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand
|
||||||
|
|
||||||
protected Session engineSession;
|
protected Session engineSession;
|
||||||
|
|
||||||
|
protected DeviceHelper deviceHandleHelper;
|
||||||
|
|
||||||
|
protected ScreenInfo screenInfo;
|
||||||
|
|
||||||
|
protected OcrHelper ocrHelper;
|
||||||
|
|
||||||
|
public AbstractAutomationHandler() {
|
||||||
|
ocrHelper = new CloudTestOcrHelper();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void automationHandle(CmdAutomationRequest request, Session engineSession) {
|
public void automationHandle(CmdAutomationRequest request, Session engineSession) {
|
||||||
String cmd = request.getCmd();
|
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
|
private AndroidAgentSession syncAgentSession; //同步处理设备指令的AgentSession
|
||||||
|
|
||||||
public AndroidAutomationHandler(AdbDevice currentDevice,Session session) throws IOException {
|
public AndroidAutomationHandler(AdbDevice currentDevice,Session session) throws IOException {
|
||||||
|
super();
|
||||||
this.adbDevice = currentDevice;
|
this.adbDevice = currentDevice;
|
||||||
this.serial = currentDevice.getSerial();
|
this.serial = currentDevice.getSerial();
|
||||||
this.engineSession = session;
|
this.engineSession = session;
|
||||||
|
@ -170,6 +171,11 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initScreenData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pressHomeKey(CmdAutomationRequest request) {
|
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.constants.UpperParamKey;
|
||||||
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
|
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest;
|
||||||
import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse;
|
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.constants.HandCommand;
|
||||||
import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
|
import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager;
|
||||||
import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager;
|
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.service.IosDebuggerServiceImpl;
|
||||||
import net.northking.cctp.upperComputer.utils.HttpUtils;
|
import net.northking.cctp.upperComputer.utils.HttpUtils;
|
||||||
import net.northking.cctp.upperComputer.utils.SpringUtils;
|
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.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.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -59,9 +60,8 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
|
||||||
|
|
||||||
private PhoneEntity phoneEntity;
|
private PhoneEntity phoneEntity;
|
||||||
|
|
||||||
private IosDeviceHandleHelper deviceHandleHelper;
|
|
||||||
|
|
||||||
public IosAutomationHandler(PhoneEntity phoneEntity, Session session) {
|
public IosAutomationHandler(PhoneEntity phoneEntity, Session session) {
|
||||||
|
super();
|
||||||
this.phoneEntity = phoneEntity;
|
this.phoneEntity = phoneEntity;
|
||||||
this.serial = phoneEntity.getUdid();
|
this.serial = phoneEntity.getUdid();
|
||||||
this.engineSession = session;
|
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
|
@Override
|
||||||
public void getVerificationCodeByNode(CmdAutomationRequest request) {
|
public void getVerificationCodeByNode(CmdAutomationRequest request) {
|
||||||
|
@ -624,8 +602,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
|
||||||
if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) {
|
if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) {
|
||||||
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
|
response = CmdAutomationResponse.builderFailure(request, "元素不存在");
|
||||||
} else {
|
} else {
|
||||||
File shotFile = deviceHandleHelper.getScreenShotFile(serial, uiNodeData.getX(), uiNodeData.getY(), uiNodeData.getWidth(), uiNodeData.getHeight(), phoneEntity.getScreenWidth(), phoneEntity.getScreenHeight());
|
int x = uiNodeData.getX() * phoneEntity.getScale();
|
||||||
String targetBase64 = getFileBase64(shotFile);
|
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)) {
|
if (StringUtils.isBlank(targetBase64)) {
|
||||||
response = CmdAutomationResponse.builderFailure(request, "未获取到ocr区域");
|
response = CmdAutomationResponse.builderFailure(request, "未获取到ocr区域");
|
||||||
return;
|
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
|
@Override
|
||||||
public void handleNodeByOcr(CmdAutomationRequest request) {
|
public void handleNodeByOcr(CmdAutomationRequest request) {
|
||||||
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr点击控件失败").withData(false);
|
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr点击控件失败").withData(false);
|
||||||
|
@ -1425,10 +1367,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
|
||||||
return allPoint.get(index - 1);
|
return allPoint.get(index - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFileBase64(File file) {
|
|
||||||
String encode = Base64.encode(file);
|
|
||||||
return encode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNodeByImage(CmdAutomationRequest request) {
|
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
|
@Override
|
||||||
public void pressHomeKey(CmdAutomationRequest request) {
|
public void pressHomeKey(CmdAutomationRequest request) {
|
||||||
CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "点击home键失败").withData(false);
|
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
|
@Override
|
||||||
public void getElementValueByPathOcr(CmdAutomationRequest request) {
|
public void getElementValueByPathOcr(CmdAutomationRequest request) {
|
||||||
|
@ -2339,14 +2250,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getOcrAreaByBodeToBase64(UiNodeData uiNodeData) {
|
private String getOcrAreaByBodeToBase64(UiNodeData uiNodeData) {
|
||||||
Integer x = uiNodeData.getX();
|
int x = uiNodeData.getX() * phoneEntity.getScale();
|
||||||
Integer y = uiNodeData.getY();
|
int y = uiNodeData.getY() * phoneEntity.getScale();
|
||||||
Integer screenWidth = phoneEntity.getScreenWidth();
|
int width = uiNodeData.getWidth() * phoneEntity.getScale();
|
||||||
Integer screenHeight = phoneEntity.getScreenHeight();
|
int height = uiNodeData.getHeight() * phoneEntity.getScale();
|
||||||
Integer cutWidth = uiNodeData.getWidth();
|
logger.debug("拿到的截图参数----->>>>>x:{},y:{},cutWidth:{},cutHeight:{}", x, y, width, height);
|
||||||
Integer cutHeight = uiNodeData.getHeight();
|
File file = deviceHandleHelper.getScreenShotFile(phoneEntity.getUdid(), x, y, width, height);
|
||||||
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);
|
|
||||||
logger.debug("ocr截图:{}", file.getAbsolutePath());
|
logger.debug("ocr截图:{}", file.getAbsolutePath());
|
||||||
String base64Str = getFileBase64(file);
|
String base64Str = getFileBase64(file);
|
||||||
return base64Str;
|
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() {
|
private NKAgent getNkAgent() {
|
||||||
int times = 0;
|
int times = 0;
|
||||||
NKAgent nkAgent = null;
|
NKAgent nkAgent = null;
|
||||||
|
@ -2705,45 +2545,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler {
|
||||||
return nkAgent;
|
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 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() {
|
public String getOcrGetAllTextNum() {
|
||||||
return ocrGetAllTextNum;
|
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;
|
package net.northking.cctp.upperComputer.deviceManager;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
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.CdDeviceModel;
|
||||||
import net.northking.cctp.upperComputer.entity.CdDeviceRegisterDto;
|
import net.northking.cctp.upperComputer.entity.CdDeviceRegisterDto;
|
||||||
import net.northking.cctp.upperComputer.entity.CdMobileBrand;
|
import net.northking.cctp.upperComputer.entity.CdMobileBrand;
|
||||||
|
@ -23,6 +24,7 @@ import java.net.URI;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author : yineng.huang
|
* @author : yineng.huang
|
||||||
|
@ -32,6 +34,8 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(AbstractDeviceManager.class);
|
private Logger logger = LoggerFactory.getLogger(AbstractDeviceManager.class);
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, ScreenInfo> screenInfoMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publishDeviceAgentAlready(String serial) {
|
public void publishDeviceAgentAlready(String serial) {
|
||||||
SpringUtils.getBean(DeviceConnectionService.class).deviceAgentAlready(serial);
|
SpringUtils.getBean(DeviceConnectionService.class).deviceAgentAlready(serial);
|
||||||
|
@ -190,4 +194,9 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana
|
||||||
public void offlineDevice(String serial) {
|
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) {
|
public boolean checkMobileIsOnline(String serial) {
|
||||||
AndroidDeviceInitThread initThread = onlineDeviceInitMap.get(serial);
|
AndroidDeviceInitThread initThread = onlineDeviceInitMap.get(serial);
|
||||||
if (null != initThread && !initThread.isInterrupted() && initThread.isAlive()) {
|
if (null != initThread && !initThread.isInterrupted() && initThread.isAlive()) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package net.northking.cctp.upperComputer.deviceManager;
|
package net.northking.cctp.upperComputer.deviceManager;
|
||||||
|
|
||||||
|
import net.northking.cctp.upperComputer.automation.entity.ScreenInfo;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -54,4 +56,9 @@ public interface DeviceManager {
|
||||||
* @param serial
|
* @param serial
|
||||||
*/
|
*/
|
||||||
void offlineDevice(String 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();
|
interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean checkMobileIsOnline(String serial) {
|
public boolean checkMobileIsOnline(String serial) {
|
||||||
PhoneEntity entity = this.phoneMap.get(serial);
|
PhoneEntity entity = this.phoneMap.get(serial);
|
||||||
if (null != entity && entity.getStatus()) {
|
if (null != entity && entity.getStatus()) {
|
||||||
|
|
|
@ -70,8 +70,11 @@ public class UpperComputerManager {
|
||||||
List<String> onlineAndroidDeviceIds = AndroidDeviceManager.getInstance().getOnlineDevice();
|
List<String> onlineAndroidDeviceIds = AndroidDeviceManager.getInstance().getOnlineDevice();
|
||||||
//ios
|
//ios
|
||||||
List<String> onlineIosDeviceIds = IOSDeviceManager.getInstance().getOnlineDevice();
|
List<String> onlineIosDeviceIds = IOSDeviceManager.getInstance().getOnlineDevice();
|
||||||
|
//harmony
|
||||||
|
List<String> onlineHarmonyDeviceIds = HarmonyDeviceManager.getInstance().getOnlineDevice();
|
||||||
onlineDeviceIds.addAll(onlineAndroidDeviceIds);
|
onlineDeviceIds.addAll(onlineAndroidDeviceIds);
|
||||||
onlineDeviceIds.addAll(onlineIosDeviceIds);
|
onlineDeviceIds.addAll(onlineIosDeviceIds);
|
||||||
|
onlineDeviceIds.addAll(onlineHarmonyDeviceIds);
|
||||||
this.computerHeartInfo.setDeviceIds(onlineDeviceIds);
|
this.computerHeartInfo.setDeviceIds(onlineDeviceIds);
|
||||||
return this.computerHeartInfo;
|
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