From 565c5f1ee92aa6a9dd1b6433eae2c44626053bb9 Mon Sep 17 00:00:00 2001 From: "yineng.huang" Date: Thu, 13 Feb 2025 18:16:37 +0800 Subject: [PATCH] =?UTF-8?q?harmony=E8=AE=BE=E5=A4=87=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E4=B8=8A=E4=BD=8D=E6=9C=BA=E3=80=81=E6=8A=95=E5=B1=8F=EF=BC=8C?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E3=80=81=E4=BB=A5=E5=8F=8A=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E3=80=81=E8=AE=A1=E5=88=92=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../debug/service/impl/DebugServiceImpl.java | 4 +- .../device/bean/MobileDeviceConnection.java | 104 +- .../service/impl/DeviceConnectionManager.java | 3 + .../northking/cctp/se/util/AndroidUtil.java | 102 +- .../northking/cctp/se/util/HarmonyUtil.java | 4 + .../net/northking/cctp/se/util/IosUtil.java | 171 +-- .../net/northking/cctp/se/util/PhoneUtil.java | 172 +++ .../service/AtuPlanInfoApiServiceImpl.java | 11 +- .../executePlan/constants/MsgConstant.java | 1 + .../planScript/AtuPlanScriptResultDto.java | 11 + .../feign/dto/AtuScriptInfoResultDto.java | 12 + .../api/app/service/FileMinioServiceImpl.java | 234 ++- .../service/AtuScriptInfoApiServiceImpl.java | 8 + .../scriptcase/constants/ScriptConstant.java | 1 + .../atuScriptInfo/AtuScriptInfoResultDto.java | 13 + .../cctp/scriptcase/tools/ByteUtils.java | 99 ++ .../scriptcase/tools/InputStreamUtils.java | 128 ++ .../cctp/scriptcase/tools/Preconditions.java | 483 ++++++ .../cctp/scriptcase/tools/hapUtil/Hap.java | 161 ++ .../tools/hapUtil/module/Ability.java | 152 ++ .../scriptcase/tools/hapUtil/module/App.java | 172 +++ .../hapUtil/module/ExtensionAbility.java | 62 + .../tools/hapUtil/module/HapModule.java | 30 + .../tools/hapUtil/module/Metadatum.java | 40 + .../tools/hapUtil/module/Module.java | 178 +++ .../tools/hapUtil/module/Skill.java | 38 + .../tools/hapUtil/pack/Ability.java | 30 + .../tools/hapUtil/pack/ApiVersion.java | 40 + .../scriptcase/tools/hapUtil/pack/App.java | 40 + .../scriptcase/tools/hapUtil/pack/Distro.java | 50 + .../tools/hapUtil/pack/ExtensionAbility.java | 32 + .../tools/hapUtil/pack/HapPackInfo.java | 32 + .../scriptcase/tools/hapUtil/pack/Module.java | 72 + .../tools/hapUtil/pack/Package.java | 52 + .../tools/hapUtil/pack/Summary.java | 32 + .../tools/hapUtil/pack/Version.java | 30 + .../tools/hapUtil/resources/IdSet.java | 12 + .../tools/hapUtil/resources/IndexHeader.java | 7 + .../tools/hapUtil/resources/KeyParam.java | 26 + .../tools/hapUtil/resources/KeyType.java | 32 + .../hapUtil/resources/LimitKeyConfig.java | 15 + .../tools/hapUtil/resources/RecordItem.java | 53 + .../tools/hapUtil/resources/ResType.java | 37 + .../hapUtil/resources/ResourcesIndex.java | 137 ++ .../mybatis/ext/AtuScriptInfo.Dao.xml | 2 +- .../automation/AutomationWebSocketServer.java | 20 + .../automation/constants/Command.java | 4 +- .../automation/entity/ScreenInfo.java | 48 + .../handler/AbstractAutomationHandler.java | 380 +++++ .../handler/AndroidAutomationHandler.java | 6 + .../handler/HarmonyAutomationHandler.java | 939 ++++++++++++ .../handler/IosAutomationHandler.java | 258 +--- .../automation/utils/CloudTestOcrHelper.java | 241 +++ .../automation/utils/OcrHelper.java | 71 + .../config/HttpRequestPathConfig.java | 32 + .../constants/HarmonyKeyBoardCodeEnum.java | 171 +++ .../deviceManager/AbstractDeviceManager.java | 9 + .../deviceManager/AndroidDeviceManager.java | 1 + .../deviceManager/DeviceManager.java | 7 + .../deviceManager/HarmonyDeviceManager.java | 321 ++++ .../deviceManager/IOSDeviceManager.java | 1 + .../deviceManager/UpperComputerManager.java | 3 + .../listener/HarmonyDeviceListener.java | 23 + .../screen/HarmonyScreenResponseThread.java | 83 + .../deviceManager/thread/HarmonyProvider.java | 497 ++++++ .../deviceManager/thread/InitDevice.java | 15 + .../deviceManager/thread/util/IIOJob.java | 16 + .../deviceManager/thread/util/IOJob.java | 248 +++ .../deviceManager/thread/util/IOScope.java | 9 + .../thread/util/LaunchBlock.java | 9 + .../deviceManager/thread/util/ThreadJob.java | 124 ++ .../thread/util/ThreadScope.java | 250 +++ .../driver/harmony/HarmonyDevice.java | 269 ++++ .../harmony/hdc/DeviceActionResult.java | 13 + .../driver/harmony/hdc/ForwardRule.java | 184 +++ .../driver/harmony/hdc/HDCConnectStatus.java | 34 + .../driver/harmony/hdc/HDCConnectType.java | 45 + .../driver/harmony/hdc/HDCDevice.java | 90 ++ .../driver/harmony/hdc/HDCDeviceListener.java | 17 + .../driver/harmony/hdc/HDCDeviceObserver.java | 136 ++ .../driver/harmony/hdc/HDCSession.java | 244 +++ .../driver/harmony/hdc/HarmonyKeyCode.java | 1347 +++++++++++++++++ .../upperComputer/driver/harmony/hdc/Hdc.java | 583 +++++++ .../driver/harmony/hyppium/AgentABI.java | 7 + .../driver/harmony/hyppium/HyppiumAgent.java | 837 ++++++++++ .../harmony/hyppium/RequestCodeProvider.java | 20 + .../driver/harmony/hyppium/RequestData.java | 46 + .../driver/harmony/hyppium/Response.java | 10 + .../harmony/hyppium/StaticRequestData.java | 18 + .../harmony/hyppium/action/AbstractTouch.java | 12 + .../harmony/hyppium/action/CaptureLayout.java | 22 + .../hyppium/action/GetDisplayRotation.java | 20 + .../hyppium/action/GetDisplaySize.java | 20 + .../driver/harmony/hyppium/action/Pinch.java | 18 + .../harmony/hyppium/action/PressBack.java | 23 + .../hyppium/action/PressDownVolume.java | 19 + .../harmony/hyppium/action/PressHome.java | 23 + .../harmony/hyppium/action/PressPowerKey.java | 19 + .../hyppium/action/PressRecentApp.java | 23 + .../harmony/hyppium/action/PressUpVolume.java | 19 + .../hyppium/action/RotationDisplay.java | 12 + .../action/StartCaptureScreenStream.java | 18 + .../hyppium/action/StartCaptureUiAction.java | 22 + .../action/StopCaptureScreenStream.java | 19 + .../hyppium/action/StopCaptureUiAction.java | 22 + .../harmony/hyppium/action/TouchDown.java | 10 + .../harmony/hyppium/action/TouchMove.java | 10 + .../harmony/hyppium/action/TouchUp.java | 10 + .../hyppium/data/CaptureLayoutData.java | 8 + .../hyppium/data/CaptureScreenData.java | 9 + .../hyppium/data/DisplayRotationData.java | 11 + .../harmony/hyppium/data/DisplaySize.java | 9 + .../harmony/hyppium/data/PositionData.java | 11 + .../harmony/hyppium/data/ResultData.java | 5 + .../harmony/hyppium/data/ScaleData.java | 16 + .../harmony/hyppium/data/UiEventData.java | 81 + .../harmony/hyppium/data/UiEventFinger.java | 111 ++ .../harmony/hyppium/data/UiEventResponse.java | 55 + .../harmony/hyppium/data/UiEventStage.java | 25 + .../driver/harmony/ui/UiAttribute.java | 293 ++++ .../driver/harmony/ui/UiComponent.java | 78 + .../driver/harmony/ui/UiRect.java | 83 + .../driver/harmony/ui/XMLEntity.java | 18 + .../upperComputer/init/UpperComputerInit.java | 5 + .../service/impl/ConnectionServiceImpl.java | 39 +- .../AbstractDeviceHelper.java} | 63 +- .../utils/deviceHepler/DeviceHelper.java | 32 + .../android/AndroidDeviceHelper.java | 170 +++ .../harmony/HarmonyHandlerHelper.java | 207 +++ .../ios/IosDeviceHandleHelper.java | 46 + .../ios/LinuxAndWindowsIosHandleHelper.java | 239 +++ .../ios/MacIosHandleHelper.java | 116 +- .../ios/LinuxAndWindowsIosHandleHelper.java | 301 ---- .../webSocket/DeviceConnectionWebSocket.java | 3 + .../upperComputer/webSocket/entity/Point.java | 4 +- .../AbstractIosMessageHandlerThread.java | 2 +- .../thread/AndroidMessageHandlerThread.java | 2 +- .../thread/HarmonyGetNodeTreeThread.java | 163 ++ .../thread/HarmonyMessageHandlerThread.java | 595 ++++++++ .../webSocket/thread/MessageHandler.java | 2 + .../cctp/mobile/thread/ProcessMsgThread.java | 10 +- .../cctp-test-element-mobile-hnos/pom.xml | 93 ++ .../mobile/hnos/MobileHnosLibrary.java | 52 + .../mobile/hnos/constants/CommandId.java | 34 + .../mobile/hnos/constants/UpperParamKey.java | 145 ++ .../mobile/hnos/constants/UsingType.java | 20 + .../hnos/entity/ElementHandleParam.java | 412 +++++ .../mobile/hnos/entity/PointMessage.java | 106 ++ .../mobile/hnos/entity/StepTarget.java | 45 + .../element/mobile/hnos/enums/AppEnum.java | 32 + .../mobile/hnos/enums/SwipeDirection.java | 34 + .../mobile/hnos/enums/TextDirection.java | 33 + .../mobile/hnos/enums/WhetherOrNotEnum.java | 35 + .../mobile/hnos/keywords/HnosTools.java | 605 ++++++++ .../hnos/utils/AutomationHandleUtil.java | 1329 ++++++++++++++++ .../mobile/hnos/utils/CommonUtils.java | 64 + .../element/mobile/hnos/utils/HttpUtils.java | 95 ++ .../element/mobile/hnos/utils/RegexUtils.java | 51 + .../resources/META-INF/cctp-class.factories | 1 + .../src/main/resources/changelog.md | 5 + 160 files changed, 16057 insertions(+), 1098 deletions(-) create mode 100644 cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/HarmonyUtil.java create mode 100644 cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/PhoneUtil.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/ByteUtils.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/InputStreamUtils.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/Preconditions.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/Hap.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Ability.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/App.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/ExtensionAbility.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/HapModule.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Metadatum.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Module.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Skill.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Ability.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ApiVersion.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/App.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Distro.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ExtensionAbility.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/HapPackInfo.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Module.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Package.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Summary.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Version.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IdSet.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IndexHeader.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyParam.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyType.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/LimitKeyConfig.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/RecordItem.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResType.java create mode 100644 cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResourcesIndex.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/entity/ScreenInfo.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/HarmonyAutomationHandler.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/CloudTestOcrHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/OcrHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/constants/HarmonyKeyBoardCodeEnum.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/HarmonyDeviceManager.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/listener/HarmonyDeviceListener.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/screen/HarmonyScreenResponseThread.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/HarmonyProvider.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/InitDevice.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IIOJob.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOJob.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOScope.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/LaunchBlock.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadJob.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadScope.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/HarmonyDevice.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/DeviceActionResult.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/ForwardRule.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectStatus.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectType.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDevice.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceListener.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceObserver.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCSession.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HarmonyKeyCode.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/Hdc.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/AgentABI.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/HyppiumAgent.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestCodeProvider.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/Response.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/StaticRequestData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/AbstractTouch.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/CaptureLayout.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplayRotation.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplaySize.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/Pinch.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressBack.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressDownVolume.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressHome.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressPowerKey.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressRecentApp.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressUpVolume.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/RotationDisplay.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureScreenStream.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureUiAction.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureScreenStream.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureUiAction.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchDown.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchMove.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchUp.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureLayoutData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureScreenData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplayRotationData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplaySize.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/PositionData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ResultData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ScaleData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventData.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventFinger.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventResponse.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventStage.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiAttribute.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiComponent.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiRect.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/XMLEntity.java rename cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/{ios/IosDeviceHandleHelper.java => deviceHepler/AbstractDeviceHelper.java} (58%) create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/DeviceHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/android/AndroidDeviceHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/harmony/HarmonyHandlerHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/IosDeviceHandleHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/LinuxAndWindowsIosHandleHelper.java rename cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/{ => deviceHepler}/ios/MacIosHandleHelper.java (58%) delete mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/LinuxAndWindowsIosHandleHelper.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyGetNodeTreeThread.java create mode 100644 cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyMessageHandlerThread.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/pom.xml create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/CommandId.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UpperParamKey.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UsingType.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/ElementHandleParam.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/PointMessage.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/StepTarget.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/AppEnum.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/SwipeDirection.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/TextDirection.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/WhetherOrNotEnum.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/keywords/HnosTools.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/AutomationHandleUtil.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/CommonUtils.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/HttpUtils.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/RegexUtils.java create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/META-INF/cctp-class.factories create mode 100644 cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/changelog.md diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/debug/service/impl/DebugServiceImpl.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/debug/service/impl/DebugServiceImpl.java index f02e53a..7e77fb1 100644 --- a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/debug/service/impl/DebugServiceImpl.java +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/debug/service/impl/DebugServiceImpl.java @@ -154,8 +154,8 @@ public class DebugServiceImpl implements DebugService { Object type = request.getData().get("type"); boolean mobileFlag = false; if (type != null) { - // 0-接口 1- b/s 2 c/s 3 安卓 4 ios - mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + ""); + // 0-接口 1- b/s 2 c/s 3 安卓 4 ios 5 harmony + mobileFlag = "3".equalsIgnoreCase(type + "") || "4".equalsIgnoreCase(type + "") || "5".equalsIgnoreCase(type+""); } if (mobileFlag && DebugerConst.CommandConst.CMD_DEVICE_INIT.equalsIgnoreCase(request.getCmd())) { //设置应用下载的url Map data = request.getData(); diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/bean/MobileDeviceConnection.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/bean/MobileDeviceConnection.java index d8e488d..7ca7f5d 100644 --- a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/bean/MobileDeviceConnection.java +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/bean/MobileDeviceConnection.java @@ -135,6 +135,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { driver = createAndroidDriver(appiumServerUrl, appiumHost + ":8808", deviceInfo.getDeviceId(), null); } else if ("1".equalsIgnoreCase(deviceInfo.getPlatform()) || "ios".equalsIgnoreCase(deviceInfo.getPlatform())) { driver = createIosDriver(appiumServerUrl, deviceInfo, null); + }else if ("2".equalsIgnoreCase(deviceInfo.getPlatform()) || "harmony".equalsIgnoreCase(deviceInfo.getPlatform())) { + driver = createHarmonyDriver(); } if (driver != null) { log.info("Driver for device[{}] created successfully.", deviceInfo.getDeviceId()); @@ -145,6 +147,18 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { return driver; } + private DeviceDriver createHarmonyDriver() { + MobileDeviceDriver driver = null; + log.warn("设备【{}】采用自研方式自动化", deviceInfo.getDeviceId()); + try { + driver = new MobileDeviceDriver(this.remoteAddress,deviceInfo.getDeviceId(),"harmony"); + } catch (Exception e) { + log.error("设备【{}】创建MobileDriver失败,地址:{}", deviceInfo.getDeviceId(),this.remoteAddress, e); + throw new ExecuteException("设备驱动创建失败,请稍后再试"); + } + return driver; + } + private DeviceDriver createIosDriver(URL appiumServerUrl, DeviceInfo deviceInfo, Map others) { MobileAutomationSelectorConfig selectorConfig = SpringUtils.getBean(MobileAutomationSelectorConfig.class); log.info("拿到的自动化类型:{}", selectorConfig.getType()); @@ -501,21 +515,11 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { watchAppActive = new StopWatch(); watchAppActive.start(); } - if (this.deviceDriver instanceof MobileDriver) { - log.debug("设备【{}】采取自研自动化....................",this.deviceInfo.getDeviceId()); - String uuid = this.deviceInfo.getDeviceId(); - IosUtil.activeApp(uuid, appPackage, remoteAddress); - } else { - log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId()); - AppiumDriver driver = (AppiumDriver) this.deviceDriver; - try { - String uuid = (String) driver.getCapabilities().getCapability(UDID); - if (driver instanceof AndroidDriver) { - AndroidUtil.activeApp(uuid, appPackage, remoteAddress); - } else if (driver instanceof IOSDriver) { - IosUtil.activeApp(uuid, appPackage, remoteAddress); - } - } finally { + try { + log.debug("设备【{}】开始启动App....................", this.deviceInfo.getDeviceId()); + PhoneUtil.activeApp(this.deviceInfo.getDeviceId(), appPackage, remoteAddress, deviceInfo.getPlatform()); + log.debug("设备【{}】完成启动App....................", this.deviceInfo.getDeviceId()); + } finally { if (isRecord && null != watchAppActive) { watchAppActive.stop(); long time = watchAppActive.getTime(TimeUnit.MILLISECONDS); @@ -523,8 +527,6 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { devicePerInfo.getAppPerInfo().setAppActiveTime(time); } } - } - } private void pressHome() { @@ -576,8 +578,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { removeApp = driver.removeApp(appPackage); } return removeApp; - } else { //ios - return IosUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage); + } else { //ios和harmony + return PhoneUtil.removeApp(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform()); } } @@ -604,8 +606,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { appInstalled = driver.isAppInstalled(appPackage); } return appInstalled; - } else { //ios - return IosUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage); + } else { + return PhoneUtil.isAppInstalled(this.remoteAddress, deviceInfo.getDeviceId(), appPackage,deviceInfo.getPlatform()); } } @@ -621,10 +623,9 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { log.warn("启动app时前停止。。。。。。。。。。"); throw new ExecuteException("执行停止"); } - if ("0".equals(deviceInfo.getPlatform())) { - return AndroidUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage); - } - return true; + boolean success = PhoneUtil.cleanAppData(deviceInfo.getConnectAddress() + ":" + (null == deviceInfo.getPort() ? defaultUpperPort : deviceInfo.getPort()), deviceId, appPackage, deviceInfo.getPlatform()); + log.debug("设备【{}】清除数据结果:{}",deviceId,success); + return success; } @@ -673,31 +674,11 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { throw new ExecuteException("执行停止"); } try { - boolean isOk = false; - boolean isIOS = this.deviceDriver instanceof IOSDriver; - boolean isAndroid = this.deviceDriver instanceof AndroidDriver; - String platform = null; - if (null != deviceInfo) { - platform = deviceInfo.getPlatform(); - } - if (null == platform) { - if (isAndroid) { - platform = "0"; - } else if (isIOS) { - platform = "1"; - } - log.debug("设备信息未获取到平台,直接根据驱动判断平台(1-IOS 0-安卓):{}", platform); - } + boolean success = false; try { - if (isAndroid) { - isOk = AndroidUtil.installApp(this.remoteAddress, deviceId, appPath, platform, packageName); - } else if (isIOS) { - isOk = IosUtil.installApp(this.remoteAddress, deviceId, appPath, platform); - } else { - log.warn("driver类型未知,安装应用失败!"); - } + success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform()); long t3 = System.currentTimeMillis(); - log.debug("单独安装应用耗时:{}", (t3 - t2) / 1000); + log.debug("单独安装应用耗时:{},结果:{}", (t3 - t2) / 1000,success); } catch (Exception e) { log.warn("设备【{}】安装应用【{}】出现异常,重试一次。。。", deviceId, packageName, e); try { @@ -717,15 +698,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { t2 = System.currentTimeMillis(); log.debug("单独下载应用耗时:{}", (t2 - t1) / 1000); log.info("download app finish,start install ............"); - if (isAndroid) { - // 回到桌面 - pressHome(); - isOk = AndroidUtil.installApp(this.remoteAddress, deviceId, appPath, platform, packageName); - } else if (isIOS) { - isOk = IosUtil.installApp(this.remoteAddress, deviceId, appPath, platform); - } else { - log.warn("driver类型未知,安装应用失败!"); - } + success = PhoneUtil.installApp(this.remoteAddress, deviceId, appPath, deviceInfo.getPlatform()); } // if (this.deviceDriver instanceof AndroidDriver) { // AppiumDriver driver = (AppiumDriver) this.deviceDriver; @@ -770,11 +743,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { @Override public void startRecord() { - if ("0".equals(deviceInfo.getPlatform())) { - AndroidUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"),deviceInfo.getOtherInfo().get("tenantId")); - } else { - IosUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"), deviceInfo.getOtherInfo().get("tenantId"),deviceInfo.getResolution()); - } + PhoneUtil.startRecord(this.remoteAddress, deviceInfo.getDeviceId(), deviceInfo.getOtherInfo().get("taskId"),deviceInfo.getOtherInfo().get("tenantId"),deviceInfo.getResolution(),deviceInfo.getPlatform()); } @Override @@ -786,11 +755,8 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { if (ObjectUtil.isNull(initData.get("save"))) { throw new RuntimeException("视频保存标识不能为空"); } - if ("0".equals(deviceInfo.getPlatform())) { - result = AndroidUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData); - } else { - result = IosUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData); - } + result = PhoneUtil.stopRecord(this.remoteAddress, deviceInfo.getDeviceId(), initData,deviceInfo.getPlatform()); + log.debug("任务{},得到录频保存地址:{}",initData.get("taskId"),result); return result; } @@ -862,7 +828,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { if (this.deviceDriver instanceof MobileDriver) { log.debug("设备【{}】采取自研自动化重新启动App....................",this.deviceInfo.getDeviceId()); String uuid = this.deviceInfo.getDeviceId(); - IosUtil.terminateApp(uuid, appPackage, remoteAddress); + PhoneUtil.terminateApp(uuid, appPackage, remoteAddress,deviceInfo.getPlatform()); } else { log.debug("设备【{}】采取appium自动化....................",this.deviceInfo.getDeviceId()); AppiumDriver deviceDriver = (AppiumDriver) this.deviceDriver; @@ -881,7 +847,7 @@ public class MobileDeviceConnection extends AbstractDeviceConnection { } } else if (deviceDriver instanceof IOSDriver) { String uuid = (String) deviceDriver.getCapabilities().getCapability(UDID); - IosUtil.terminateApp(uuid, appPackage, remoteAddress); + IosUtil.terminateApp(uuid, appPackage, remoteAddress,deviceInfo.getPlatform()); } } diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/service/impl/DeviceConnectionManager.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/service/impl/DeviceConnectionManager.java index 43a4b08..de7bfd2 100644 --- a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/service/impl/DeviceConnectionManager.java +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/device/service/impl/DeviceConnectionManager.java @@ -42,6 +42,7 @@ public class DeviceConnectionManager implements DeviceConnectionService { ResponseEntity resEntity = restTemplate.getForEntity(url, Map.class); Map resultBody = resEntity.getBody(); DeviceInfo deviceInfo = new DeviceInfo(); + log.debug("查询得到的设备信息:{}", JSON.toJSONString(resultBody)); if (resultBody != null && (boolean)resultBody.get("success") && resultBody.get("data") != null) { // 请求成功,获取到设备信息 Map data = (Map) resultBody.get("data"); log.info("设备【token-{}】请求结果:{}", deviceToken, JsonUtils.toJson(data)); @@ -64,6 +65,8 @@ public class DeviceConnectionManager implements DeviceConnectionService { Object forwardPort = deviceMap.get("forwardPort"); log.debug("拿到设备【{}】在上位机用于adb转发的端口:{}",deviceInfo.getDeviceId(),forwardPort); deviceInfo.setForwardPort(Integer.parseInt(forwardPort+"")); + }else if ("2".equalsIgnoreCase(deviceInfo.getPlatform())) { //harmony + log.debug("该设备【{}】是harmony设备",deviceInfo.getDeviceId()); } } else { // pc设备 deviceInfo.setMobile(false); diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/AndroidUtil.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/AndroidUtil.java index 6e84d4a..6f96eed 100644 --- a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/AndroidUtil.java +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/AndroidUtil.java @@ -10,19 +10,18 @@ import org.springframework.http.HttpEntity; import java.util.Map; -public class AndroidUtil { +public class AndroidUtil extends PhoneUtil{ private static final Logger logger = LoggerFactory.getLogger(AndroidUtil.class); - private static final String CLEAN_APP_DATA_URL = "http://%s/engine/cleanData"; + private static final String GET_PACKAGE_VERSION_URL = "http://%s/engine/getPackageVersion"; private static final String ALL_SCREEN_SHOT_URL = "http://%s/engine/uploadShotAllScreen"; private static final String START_RECORD_URL = "http://%s/engine/startRecord"; private static final String END_RECORD_URL = "http://%s/engine/endRecord"; - private static final String INSTALL_APP = "http://%s/engine/installApp"; - private static final String ACTIVE_APP = "http://%s/engine/activeApp"; private static final String RELEASE_ADB_FORWARD_PORT = "http://%s/engine/releaseAdbForwardPort"; + public static boolean releaseAdbForwardPort(String remoteAddress,String deviceId){ try { DebuggerDeviceInfo info = new DebuggerDeviceInfo(); @@ -35,18 +34,6 @@ public class AndroidUtil { } } - public static boolean cleanAppData(String remoteAddress,String deviceId,String appPackage){ - try { - DebuggerDeviceInfo info = new DebuggerDeviceInfo(); - info.setDeviceId(deviceId); - info.setAppPackage(appPackage); - HttpEntity 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 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 initData) { - String tenantId = (String)initData.get("tenantId"); - Boolean save = (Boolean) initData.get("save"); - DebuggerDeviceInfo info = new DebuggerDeviceInfo(); - info.setDeviceId(deviceId); - String taskId = (String)initData.get("taskId"); - logger.info("移动任务结束录屏,任务id:"+taskId); - info.setTaskId(taskId); - info.setTenantId(tenantId); - info.setSave(save); - logger.info("结束录屏参数:{}", JSON.toJSONString(info)); - HttpEntity entity = new HttpEntity<>(info); - String result = null; - try { - result = HttpUtils.doPost(String.format(END_RECORD_URL, remoteAddress), entity, String.class); - } catch (Exception e) { - logger.error("移动任务结束录屏失败",e); - } - return result; - } - - - - public static String snapshotAllScreen(String remoteAddress, DeviceInfo deviceInfo, String tenantId, String taskId) { + public static String snapshotAllScreen(String remoteAddress, DeviceInfo deviceInfo, String tenantId) { DebuggerDeviceInfo info = new DebuggerDeviceInfo(); info.setPlatform(deviceInfo.getPlatform()); info.setDeviceId(deviceInfo.getDeviceId()); info.setResolution(deviceInfo.getResolution()); info.setTenantId(tenantId); //租户id - info.setTaskId(taskId); logger.info("参数:{}", JSON.toJSONString(info)); HttpEntity entity = new HttpEntity<>(info); String result = null; @@ -127,44 +73,4 @@ public class AndroidUtil { return result; } - public static boolean installApp(String remoteAddress, String deviceId, String appPath,String platform, String packageName) { - DebuggerDeviceInfo info = new DebuggerDeviceInfo(); - info.setDeviceId(deviceId); - info.setAppPath(appPath); - info.setPlatform(platform); - info.setAppPackage(packageName); - HttpEntity 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 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; - } } diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/HarmonyUtil.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/HarmonyUtil.java new file mode 100644 index 0000000..459d8ce --- /dev/null +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/HarmonyUtil.java @@ -0,0 +1,4 @@ +package net.northking.cctp.se.util; + +public class HarmonyUtil extends PhoneUtil{ +} diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/IosUtil.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/IosUtil.java index 06ab567..46fb1ad 100644 --- a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/IosUtil.java +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/IosUtil.java @@ -2,162 +2,59 @@ package net.northking.cctp.se.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import io.appium.java_client.AppiumDriver; +import net.northking.cctp.element.core.exception.EnvironmentException; import net.northking.cctp.element.core.exception.ExecuteException; import net.northking.cctp.se.device.bean.DebuggerDeviceInfo; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; +import java.net.URI; import java.util.Map; -public class IosUtil { +public class IosUtil extends PhoneUtil{ private static final Logger logger = LoggerFactory.getLogger(IosUtil.class); - private static final String IS_APP_INSTALLED = "http://%s/engine/isAppInstalled"; - private static final String REMOVE_APP = "http://%s/engine/removeApp"; - private static final String INSTALL_APP = "http://%s/engine/installApp"; - private static final String ACTIVE_APP = "http://%s/engine/activeApp"; - private static final String TERMINATE_APP = "http://%s/engine/terminateApp"; private static final String ALL_SCREEN_SHOT_URL = "http://%s/engine/uploadShotAllScreen"; - private static final String START_RECORD_URL = "http://%s/engine/startRecord"; - private static final String END_RECORD_URL = "http://%s/engine/endRecord"; - public static boolean isAppInstalled(String remoteAddress, String deviceId, String appPackage) { - DebuggerDeviceInfo info = new DebuggerDeviceInfo(); - info.setDeviceId(deviceId); - info.setAppPackage(appPackage); - HttpEntity entity = new HttpEntity<>(info); + + public static Double getWindowsSize(AppiumDriver driver) { + if (null == driver) { + logger.error("driver获取为空"); + throw new EnvironmentException("设备自动化驱动连接已断开,请检查驱动程序是否正常"); + } + Capabilities capabilities = driver.getCapabilities(); + //todo:这里后面确定wda的地址是多少 + String wdaUrl = (String) capabilities.getCapability("webDriverAgentUrl"); + String deviceId = (String) capabilities.getCapability("deviceName"); + String windowsUrl = wdaUrl + "/window/size"; + Double scale = 2.0; try { - return HttpUtils.doPost(String.format(IS_APP_INSTALLED, remoteAddress), entity, Boolean.class); + JSONObject object = HttpUtils.doGet(new URI(windowsUrl), JSONObject.class); + JSONObject value = object.getJSONObject("value"); + scale = value.getDoubleValue("scale"); + logger.info("手机【{}】的宽高比例:{}",deviceId,scale); + return scale; } catch (Exception e) { - logger.error("设备【{}】查询app是否安装出错", deviceId, e); - try { - Thread.sleep(1000); - } catch (InterruptedException interruptedException) { - logger.warn("设备【{}】判断是否安装app前停止。。。。。。。。。。", deviceId); - throw new ExecuteException("执行停止"); - } - return HttpUtils.doPost(String.format(IS_APP_INSTALLED, remoteAddress), entity, Boolean.class); + logger.error("获取手机【{}】宽高比例失败:{}",deviceId, e); + throw new ExecuteException("获取手机宽高比例失败"); } } - public static boolean removeApp(String remoteAddress, String deviceId, String appPackage) { - DebuggerDeviceInfo info = new DebuggerDeviceInfo(); - info.setDeviceId(deviceId); - info.setAppPackage(appPackage); - HttpEntity 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 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 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 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 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 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 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; - } + + + + + + + + + } diff --git a/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/PhoneUtil.java b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/PhoneUtil.java new file mode 100644 index 0000000..e5e106d --- /dev/null +++ b/cctp-atu/atu-engine/atu-script-engine/src/main/java/net/northking/cctp/se/util/PhoneUtil.java @@ -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 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 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 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 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 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 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 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 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 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; + } +} diff --git a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/api/service/AtuPlanInfoApiServiceImpl.java b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/api/service/AtuPlanInfoApiServiceImpl.java index f05e09b..c20c39e 100644 --- a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/api/service/AtuPlanInfoApiServiceImpl.java +++ b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/api/service/AtuPlanInfoApiServiceImpl.java @@ -1356,6 +1356,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService private AtuPlanScriptResultDto queryScriptData(List scriptList,List result) { AtuPlanScriptResultDto resultDto = new AtuPlanScriptResultDto(); ResultWrapper wrapper = scriptCaseFeignClient.checkScriptData(scriptList); + logger.debug("拿到的脚本数据:{}",JSON.toJSONString(wrapper.getData())); //脚本名称 Map scriptMap = wrapper.getData().getScriptMap(); //校验结果 @@ -3133,7 +3134,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService List mobileTypeList = mobDeviceList.stream() .filter(entrustDeviceDto -> deviceList.contains(entrustDeviceDto.getDeviceId())) .collect(Collectors.toList()); - logger.debug("设备数量为:{}", mobileTypeList.size()); + logger.debug("移动端平台:{},委托的设备为:{}", type,JSON.toJSONString(mobileTypeList)); sendEntrustMsgToQueue(entrustMsg, engineInfoListDto.getEngineInfoList(), PlanConstant.ENGINE_TYPE_MOBILE, queueName, mobileTypeList); }); @@ -3252,6 +3253,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService boolean androidDevice = false; boolean iosDevice = false; + boolean harmonyDevice = false; boolean pcDevice = false; for (DeviceList device : feignDto.getDeviceLists()) { if (PlanConstant.DEVICE_TYPE_PC.equals(device.getDeviceType())){ @@ -3265,6 +3267,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService case PlanConstant.PLATFORM_TYPE_IOS: iosDevice = true; break; + case PlanConstant.PLATFORM_TYPE_HARMONY: + harmonyDevice = true; + break; default: break; } @@ -3273,6 +3278,7 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService //用例类型 boolean hasAndroid = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID); boolean hasIOS = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_IOS); + boolean hasHarmony = (boolean) map.get(MsgConstant.QUERY_MOBDEVICE_PLATFORM_TYPE_HARMONY); boolean hasPC = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_PC); boolean hasMob = (boolean) map.get(MsgConstant.PLATFORM_TYPE_HAS_MOB); StringBuilder deviceType = new StringBuilder(); @@ -3284,6 +3290,9 @@ public class AtuPlanInfoApiServiceImpl extends AbstractExcelService if (hasIOS && !iosDevice) { deviceType.append(MsgConstant.ERROR_PLATFORM_TYPE_IOS); } + if (hasHarmony && !harmonyDevice) { + deviceType.append("【Harmony】"); + } } //PC端的校验 if (hasPC && !pcDevice) { diff --git a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/constants/MsgConstant.java b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/constants/MsgConstant.java index a642e59..ec2dc55 100644 --- a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/constants/MsgConstant.java +++ b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/constants/MsgConstant.java @@ -133,6 +133,7 @@ public class MsgConstant { // public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_IOS= "hasIOS"; public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_ANDROID= "hasAndroid"; + public static final String QUERY_MOBDEVICE_PLATFORM_TYPE_HARMONY= "hasHarmony"; public static final String PLATFORM_TYPE_HAS_PC= "hasPC"; public static final String PLATFORM_TYPE_HAS_MOB= "hasMob"; public static final String MAP_JSON_APPALIVE= "appAlive"; diff --git a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/dto/planScript/AtuPlanScriptResultDto.java b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/dto/planScript/AtuPlanScriptResultDto.java index be40947..498de49 100644 --- a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/dto/planScript/AtuPlanScriptResultDto.java +++ b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/dto/planScript/AtuPlanScriptResultDto.java @@ -33,6 +33,17 @@ public class AtuPlanScriptResultDto { @ApiModelProperty("是否存在接口脚本") private boolean hasAPI; + @ApiModelProperty("是否存在harmony脚本") + private boolean hasHarmony; + + public boolean isHasHarmony() { + return hasHarmony; + } + + public void setHasHarmony(boolean hasHarmony) { + this.hasHarmony = hasHarmony; + } + public List getSimpleList() { return simpleList; } diff --git a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/feign/dto/AtuScriptInfoResultDto.java b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/feign/dto/AtuScriptInfoResultDto.java index ce0c1e9..a109f5e 100644 --- a/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/feign/dto/AtuScriptInfoResultDto.java +++ b/cctp-atu/atu-execute-plan/src/main/java/net/northking/cctp/executePlan/feign/dto/AtuScriptInfoResultDto.java @@ -31,6 +31,10 @@ public class AtuScriptInfoResultDto { private boolean hasAPI = false; ; + @ApiModelProperty("是否存在harmony脚本") + private boolean hasHarmony = false; + ; + @ApiModelProperty("脚本Map") private Map scriptMap; @@ -97,4 +101,12 @@ public class AtuScriptInfoResultDto { public void setHasAPI(boolean hasAPI) { this.hasAPI = hasAPI; } + + public boolean isHasHarmony() { + return hasHarmony; + } + + public void setHasHarmony(boolean hasHarmony) { + this.hasHarmony = hasHarmony; + } } diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/app/service/FileMinioServiceImpl.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/app/service/FileMinioServiceImpl.java index 9bbb723..2a31f3f 100644 --- a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/app/service/FileMinioServiceImpl.java +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/app/service/FileMinioServiceImpl.java @@ -69,7 +69,7 @@ public class FileMinioServiceImpl implements FileService { try { apkFile = new ApkFile(file); } catch (IOException e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(ScriptCaseAppError.APK_READ_ERR); } apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE); @@ -77,7 +77,7 @@ public class FileMinioServiceImpl implements FileService { try { apkMeta = apkFile.getApkMeta(); } catch (IOException e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR); } result.put(ScriptConstant.APP_NAME, apkMeta.getName()); @@ -87,7 +87,7 @@ public class FileMinioServiceImpl implements FileService { try { result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData()); } catch (IOException e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR); } //封装activity @@ -95,7 +95,7 @@ public class FileMinioServiceImpl implements FileService { try { activity = getActtivity(apkFile); } catch (Exception e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR); } result.put(ScriptConstant.ACTIVITY, activity); @@ -113,7 +113,7 @@ public class FileMinioServiceImpl implements FileService { fileMessage.setMessage(result); fileMessage.setManifest(manifest); } catch (Exception e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL); } finally { try { @@ -121,7 +121,7 @@ public class FileMinioServiceImpl implements FileService { apkFile.close(); } } catch (IOException e) { - logger.error("",e); + logger.error("", e); } } @@ -132,9 +132,9 @@ public class FileMinioServiceImpl implements FileService { try { long t1 = System.currentTimeMillis(); ServletOutputStream out = response.getOutputStream(); - simpleStorageService.getFileAndDirectResponseToHttp(out,result[1]); + simpleStorageService.getFileAndDirectResponseToHttp(out, result[1]); long t2 = System.currentTimeMillis(); - logger.debug("单独从服务器下载应用文件耗时:{}",(t2-t1)/1000); + logger.debug("单独从服务器下载应用文件耗时:{}", (t2 - t1) / 1000); } catch (Exception e) { logger.error("文件下载流异常", e); } @@ -163,7 +163,7 @@ public class FileMinioServiceImpl implements FileService { long t1 = System.currentTimeMillis(); downLoadAndReturn(response, result); long t2 = System.currentTimeMillis(); - logger.debug("下载应用文件完整耗时:{}",(t2-t1)/1000); + logger.debug("下载应用文件完整耗时:{}", (t2 - t1) / 1000); } @@ -209,7 +209,7 @@ public class FileMinioServiceImpl implements FileService { try { requestUrl = new URL(urls[i]); } catch (MalformedURLException e) { - logger.error("",e); + logger.error("", e); } try { urlCon = (HttpURLConnection) requestUrl.openConnection(); @@ -219,7 +219,7 @@ public class FileMinioServiceImpl implements FileService { inputStreams[i] = urlCon.getInputStream(); } } catch (IOException e) { - logger.error("",e); + logger.error("", e); } finally { if (urlCon == null) { urlCon.disconnect(); @@ -278,7 +278,7 @@ public class FileMinioServiceImpl implements FileService { Map result = new HashMap<>(); // 解析应用 ApkFile apkFile = null; - File file=null; + File file = null; try { String[] fileNames = MinioPathUtils.idToPath(appUrl); file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]); @@ -290,7 +290,7 @@ public class FileMinioServiceImpl implements FileService { try { apkFile = new ApkFile(file); } catch (IOException e) { - logger.info("new ApkFile(file)解析APK异常信息:{}",e.getMessage()); + logger.info("new ApkFile(file)解析APK异常信息:{}", e.getMessage()); throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR); } apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE); @@ -298,7 +298,7 @@ public class FileMinioServiceImpl implements FileService { try { apkMeta = apkFile.getApkMeta(); } catch (IOException e) { - logger.info("apkFile.getApkMeta()解析APK异常信息:{}",e.getMessage()); + logger.info("apkFile.getApkMeta()解析APK异常信息:{}", e.getMessage()); throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR); } result.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size)); @@ -310,7 +310,7 @@ public class FileMinioServiceImpl implements FileService { try { result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData()); } catch (IOException e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR); } // 封装activity @@ -318,7 +318,7 @@ public class FileMinioServiceImpl implements FileService { try { activity = getActtivity(apkFile); } catch (Exception e) { - logger.info("getActtivity(apkFile)解析APK异常信息:{}",e.getMessage()); + logger.info("getActtivity(apkFile)解析APK异常信息:{}", e.getMessage()); throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR); } result.put(ScriptConstant.ACTIVITY, activity); @@ -340,20 +340,20 @@ public class FileMinioServiceImpl implements FileService { } catch (PlatformRuntimeException e) { throw e; } catch (FileDownloadException e) { - logger.error("",e); + logger.error("", e); } finally { try { if (null != apkFile) { apkFile.close(); } - if(file!=null){ + if (file != null) { boolean delete = file.delete(); if (!delete) { logger.error("删除文件失败"); } } } catch (IOException e) { - logger.error("",e); + logger.error("", e); } } return fileMessage; @@ -364,7 +364,7 @@ public class FileMinioServiceImpl implements FileService { FileMessage fileMessage = new FileMessage(); Map map = new HashMap<>(); long size = 0; - File file =null; + File file = null; //解析应用 try { String[] fileNames = MinioPathUtils.idToPath(appUrl); @@ -378,7 +378,7 @@ public class FileMinioServiceImpl implements FileService { } catch (PlatformRuntimeException e) { throw e; } catch (Exception e) { - logger.error("",e); + logger.error("", e); throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL); } if (map.get(ScriptConstant.STATUS_ERR) != null) { @@ -399,8 +399,8 @@ public class FileMinioServiceImpl implements FileService { if (!delete) { logger.error("删除文件失败"); } - }catch (Exception e){ - logger.error("",e); + } catch (Exception e) { + logger.error("", e); } return fileMessage; } @@ -409,12 +409,7 @@ public class FileMinioServiceImpl implements FileService { public FileMessage uploadHarmonyOSLargeApp(String appUrl) { FileMessage fileMessage = new FileMessage(); long size = 0; - File file=null; - InputStream inputStream = null; - ZipInputStream zin = null; - ZipFile zf = null; - FileInputStream fileInputStream = null; - File apkFile = null; + File file = null; //解析应用 try { String[] fileNames = MinioPathUtils.idToPath(appUrl); @@ -424,107 +419,45 @@ public class FileMinioServiceImpl implements FileService { logger.info("hap文件为空"); throw new PlatformRuntimeException(MobileError.APP_IS_NULL); } - //apk文件流 - InputStream apkIn = null; - //apk文件名 - String apkFileName = null; - //hapConfig - String hapConfig = ""; - - //设置编码 - Charset charset = Charset.forName(ScriptConstant.CHARSET_FOR_NAME); - - try { - // hap包放进输入流 - fileInputStream = new FileInputStream(file); - } catch (FileNotFoundException e) { - logger.error("",e); + Hap hap = new Hap(file); + InputStream appIconInputStream = hap.getAppIcon(); //应用图片 + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] data = new byte[1024]; + int length = 0; + while ((length = appIconInputStream.read(data, 0, data.length)) > 0) { + byteArrayOutputStream.write(data, 0, length); } - inputStream = new BufferedInputStream(fileInputStream); - // 压缩包输入流 - zin = new ZipInputStream(inputStream, charset); - // 压缩包文件 - zf = new ZipFile(file); + String appName = hap.getAppName(); + String versionName = hap.getVersionName(); + long versionCode = hap.getVersionCode(); + String packageName = hap.getHapModule().getApp().getBundleName(); + Map result = new HashMap<>(); + //中文名 + result.put("appName", appName); + //包名 + result.put("packageName", packageName); + //版本 + result.put("buildVersion", versionName); + //平台 + result.put("platform", PlatformType.getCode("harmonyos")); + //app大小 + result.put("appSize", FileMinioServiceImpl.getAppSize(size)); + //下载地址 + result.put("appUrl", appUrl); + //图标 + result.put("appIcon", byteArrayOutputStream.toByteArray()); - //获取config以及apk文件 - ZipEntry ze; - while ((ze = zin.getNextEntry()) != null) { - //遍历的当前文件名 - String fileName = ze.getName(); - //当遍历到config.json - if (ScriptConstant.CONFIG_JSON_NAME.equals(fileName)) { - BufferedReader reader = new BufferedReader(new InputStreamReader(zf.getInputStream(ze))); - String line; - while ((line = reader.readLine()) != null) { - hapConfig += line; - } - if (reader != null) { - reader.close(); - } - } - //当遍历到apk文件 - if (ScriptConstant.FILE_ENDWITH_APK.equals(fileName.substring(fileName.length() - 4))) { - apkIn = zf.getInputStream(ze); - apkFileName = fileName; - } - } - // 解析 - // 将config.json转成map - JSONObject apkConfigJsonObject = JSON.parseObject(hapConfig); + fileMessage.setMessage(result); - // 将apk流转化为文件 - apkFile = new File(apkFileName); - FileUtils.copyInputStreamToFile(apkIn, apkFile); - //解析apk得到信息 - fileMessage = resolveApk(apkFile); - Map message = fileMessage.getMessage(); - message.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size)); - message.put(ScriptConstant.PLATFORM, PlatformType.getCode(ScriptConstant.PLATFORM_HARMONY)); - message.put(ScriptConstant.APP_URL, appUrl); fileMessage.setFileSaveUrl(appUrl); fileMessage.setFileUrl(appUrl); - fileMessage.setMessage(message); - + } catch (PlatformRuntimeException e) { throw e; } catch (Exception e) { - logger.error("",e); - throw new PlatformRuntimeException(ScriptCaseAppError.HAP_ERR); - }finally { - if (zf != null) { - try { - zf.close(); - } catch (IOException e) { - logger.error("",e); - } - } - if (zin != null) { - try { - zin.close(); - } catch (IOException e) { - logger.error("",e); - } - } - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - logger.error("",e); - } - } - if (fileInputStream != null) { - try { - fileInputStream.close(); - } catch (IOException e) { - logger.error("",e); - } - } - if (apkFile != null && apkFile.exists()) { - boolean delete = apkFile.delete(); - if (!delete) { - logger.error("删除文件失败"); - } - } + logger.error("", e); + throw new PlatformRuntimeException(new ErrorMessage("hap解析失败")); + } finally { if (file != null) { boolean delete = file.delete(); if (!delete) { @@ -534,4 +467,59 @@ public class FileMinioServiceImpl implements FileService { } return fileMessage; } -} + + private FileMessage resolveHapFile(JSONObject hapModuleJson) { + FileMessage fileMessage = new FileMessage(); + Map 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 manifest = new HashMap<>(8); +// manifest.put("package", apkMeta.getPackageName()); +// Map application = new HashMap<>(8); +// List> launcherActivities = new ArrayList<>(); +// Map 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; + } +} \ No newline at end of file diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/atuScript/service/AtuScriptInfoApiServiceImpl.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/atuScript/service/AtuScriptInfoApiServiceImpl.java index 7ad6aad..28c98e4 100644 --- a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/atuScript/service/AtuScriptInfoApiServiceImpl.java +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/api/atuScript/service/AtuScriptInfoApiServiceImpl.java @@ -1588,6 +1588,7 @@ public class AtuScriptInfoApiServiceImpl extends AbstractExcelService scriptMap; @@ -95,6 +99,15 @@ public class AtuScriptInfoResultDto { } public void setHasAPI(boolean hasAPI) { + this.hasAPI = hasAPI; } + + public boolean isHasHarmony() { + return hasHarmony; + } + + public void setHasHarmony(boolean hasHarmony) { + this.hasHarmony = hasHarmony; + } } diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/ByteUtils.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/ByteUtils.java new file mode 100644 index 0000000..333f4aa --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/ByteUtils.java @@ -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(); + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/InputStreamUtils.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/InputStreamUtils.java new file mode 100644 index 0000000..5094c1e --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/InputStreamUtils.java @@ -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 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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/Preconditions.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/Preconditions.java new file mode 100644 index 0000000..8423695 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/Preconditions.java @@ -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, ? extends RuntimeException> oobef, + String checkKind, + Number... args) { + List 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, ? extends RuntimeException> oobe, + int index, int length) { + return outOfBounds(oobe, "checkIndex", index, length); + } + + private static RuntimeException outOfBoundsCheckFromToIndex( + BiFunction, ? extends RuntimeException> oobe, + int fromIndex, int toIndex, int length) { + return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length); + } + + private static RuntimeException outOfBoundsCheckFromIndexSize( + BiFunction, ? extends RuntimeException> oobe, + int fromIndex, int size, int length) { + return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length); + } + + private static RuntimeException outOfBoundsCheckIndex( + BiFunction, ? extends RuntimeException> oobe, + long index, long length) { + return outOfBounds(oobe, "checkIndex", index, length); + } + + private static RuntimeException outOfBoundsCheckFromToIndex( + BiFunction, ? extends RuntimeException> oobe, + long fromIndex, long toIndex, long length) { + return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length); + } + + private static RuntimeException outOfBoundsCheckFromIndexSize( + BiFunction, ? 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. + * + *

The exception formatter accepts two arguments: a {@code String} + * describing the out-of-bounds range check that failed, referred to as the + * check kind; and a {@code List} containing the + * out-of-bound integral values that failed the check. The list of + * out-of-bound values is not modified. + * + *

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). + * + *

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: + *

{@code
+     * static final
+     * BiFunction, ArrayIndexOutOfBoundsException> AIOOBEF =
+     *     outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
+     * }
+ * 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}: + *
{@code
+     * checkIndex(index, limit, AIOOBEF);
+     * }
+ * 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: + *
{@code
+     * AIOOBEF.apply("checkIndex", List.of(index, limit));
+     * }
+ * + * @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 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 + BiFunction, X> outOfBoundsExceptionFormatter(Function f) { + // Use anonymous class to avoid bootstrap issues if this method is + // used early in startup + return new BiFunction, X>() { + @Override + public X apply(String checkKind, List args) { + return f.apply(outOfBoundsMessage(checkKind, args)); + } + }; + } + + private static String outOfBoundsMessage(String checkKind, List 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, %The {@code index} is defined to be out of bounds if any of the + * following inequalities is true: + *
    + *
  • {@code index < 0}
  • + *
  • {@code index >= length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkIndex(int index, int length, + BiFunction, 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). + * + *

The sub-range is defined to be out of bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code fromIndex > toIndex}
  • + *
  • {@code toIndex > length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkFromToIndex(int fromIndex, int toIndex, int length, + BiFunction, 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). + * + *

The sub-range is defined to be out of bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code size < 0}
  • + *
  • {@code fromIndex + size > length}, taking into account integer overflow
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + int checkFromIndexSize(int fromIndex, int size, int length, + BiFunction, 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). + * + *

The {@code index} is defined to be out of bounds if any of the + * following inequalities is true: + *

    + *
  • {@code index < 0}
  • + *
  • {@code index >= length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + long checkIndex(long index, long length, + BiFunction, 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). + * + *

The sub-range is defined to be out of bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code fromIndex > toIndex}
  • + *
  • {@code toIndex > length}
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + long checkFromToIndex(long fromIndex, long toIndex, long length, + BiFunction, 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). + * + *

The sub-range is defined to be out of bounds if any of the following + * inequalities is true: + *

    + *
  • {@code fromIndex < 0}
  • + *
  • {@code size < 0}
  • + *
  • {@code fromIndex + size > length}, taking into account integer overflow
  • + *
  • {@code length < 0}, which is implied from the former inequalities
  • + *
+ * + *

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 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 + long checkFromIndexSize(long fromIndex, long size, long length, + BiFunction, X> oobef) { + if ((length | fromIndex | size) < 0 || size > length - fromIndex) + throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length); + return fromIndex; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/Hap.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/Hap.java new file mode 100644 index 0000000..9ca07c2 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/Hap.java @@ -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; + } + + /** + * 获取应用图标 + *
+ * 记得使用完毕后关闭流 + * + * @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); + } + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Ability.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Ability.java new file mode 100644 index 0000000..c862022 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Ability.java @@ -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 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 getSkills() { + return mSkills; + } + + public void setSkills(List 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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/App.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/App.java new file mode 100644 index 0000000..8b9ec19 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/App.java @@ -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 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 getAppEnvironments() { + return mAppEnvironments; + } + + public void setAppEnvironments(List 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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/ExtensionAbility.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/ExtensionAbility.java new file mode 100644 index 0000000..57ff02e --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/ExtensionAbility.java @@ -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 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 getMetadata() { + return mMetadata; + } + + public void setMetadata(List 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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/HapModule.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/HapModule.java new file mode 100644 index 0000000..bef9694 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/HapModule.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Metadatum.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Metadatum.java new file mode 100644 index 0000000..9c21339 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Metadatum.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Module.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Module.java new file mode 100644 index 0000000..0d90912 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Module.java @@ -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 mAbilities; + @SerializedName("compileMode") + private String mCompileMode; + @SerializedName("deliveryWithInstall") + private Boolean mDeliveryWithInstall; + @SerializedName("dependencies") + private List mDependencies; + @SerializedName("description") + private String mDescription; + @SerializedName("descriptionId") + private Long mDescriptionId; + @SerializedName("deviceTypes") + private List mDeviceTypes; + @SerializedName("extensionAbilities") + private List 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 getAbilities() { + return mAbilities; + } + + public void setAbilities(List 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 getDependencies() { + return mDependencies; + } + + public void setDependencies(List 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 getDeviceTypes() { + return mDeviceTypes; + } + + public void setDeviceTypes(List deviceTypes) { + mDeviceTypes = deviceTypes; + } + + public List getExtensionAbilities() { + return mExtensionAbilities; + } + + public void setExtensionAbilities(List 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 = ability.getSkills(); + if (skill != null) { + for (Skill s : skill) { + if (s.isDefaultHome()) { + return ability; + } + } + } + } + return null; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Skill.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Skill.java new file mode 100644 index 0000000..f201a18 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/module/Skill.java @@ -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 mActions; + @SerializedName("entities") + private List mEntities; + + public List getActions() { + return mActions; + } + + public void setActions(List actions) { + mActions = actions; + } + + public List getEntities() { + return mEntities; + } + + public void setEntities(List entities) { + mEntities = entities; + } + + public boolean isDefaultHome() { + return mActions != null && mActions.contains(ACTION_SYSTEM_HOME) && mEntities != null && mEntities.contains(ENTRY_SYSTEM_HOME); + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Ability.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Ability.java new file mode 100644 index 0000000..f925cae --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Ability.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ApiVersion.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ApiVersion.java new file mode 100644 index 0000000..0c082a2 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ApiVersion.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/App.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/App.java new file mode 100644 index 0000000..ed8cfba --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/App.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Distro.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Distro.java new file mode 100644 index 0000000..4b347a8 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Distro.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ExtensionAbility.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ExtensionAbility.java new file mode 100644 index 0000000..c11964f --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/ExtensionAbility.java @@ -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 mForms; + @SerializedName("name") + private String mName; + + public List getForms() { + return mForms; + } + + public void setForms(List forms) { + mForms = forms; + } + + public String getName() { + return mName; + } + + public void setName(String name) { + mName = name; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/HapPackInfo.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/HapPackInfo.java new file mode 100644 index 0000000..e547b4b --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/HapPackInfo.java @@ -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 mPackages; + @SerializedName("summary") + private Summary mSummary; + + public List getPackages() { + return mPackages; + } + + public void setPackages(List packages) { + mPackages = packages; + } + + public Summary getSummary() { + return mSummary; + } + + public void setSummary(Summary summary) { + mSummary = summary; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Module.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Module.java new file mode 100644 index 0000000..a28e57d --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Module.java @@ -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 mAbilities; + @SerializedName("apiVersion") + private ApiVersion mApiVersion; + @SerializedName("deviceType") + private List mDeviceType; + @SerializedName("distro") + private Distro mDistro; + @SerializedName("extensionAbilities") + private List mExtensionAbilities; + @SerializedName("mainAbility") + private String mMainAbility; + + public List getAbilities() { + return mAbilities; + } + + public void setAbilities(List abilities) { + mAbilities = abilities; + } + + public ApiVersion getApiVersion() { + return mApiVersion; + } + + public void setApiVersion(ApiVersion apiVersion) { + mApiVersion = apiVersion; + } + + public List getDeviceType() { + return mDeviceType; + } + + public void setDeviceType(List deviceType) { + mDeviceType = deviceType; + } + + public Distro getDistro() { + return mDistro; + } + + public void setDistro(Distro distro) { + mDistro = distro; + } + + public List getExtensionAbilities() { + return mExtensionAbilities; + } + + public void setExtensionAbilities(List extensionAbilities) { + mExtensionAbilities = extensionAbilities; + } + + public String getMainAbility() { + return mMainAbility; + } + + public void setMainAbility(String mainAbility) { + mMainAbility = mainAbility; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Package.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Package.java new file mode 100644 index 0000000..8dccaeb --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Package.java @@ -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 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 getDeviceType() { + return mDeviceType; + } + + public void setDeviceType(List 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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Summary.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Summary.java new file mode 100644 index 0000000..b0ee078 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Summary.java @@ -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 mModules; + + public App getApp() { + return mApp; + } + + public void setApp(App app) { + mApp = app; + } + + public List getModules() { + return mModules; + } + + public void setModules(List modules) { + mModules = modules; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Version.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Version.java new file mode 100644 index 0000000..9f33111 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/pack/Version.java @@ -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; + } + +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IdSet.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IdSet.java new file mode 100644 index 0000000..2370eb7 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IdSet.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IndexHeader.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IndexHeader.java new file mode 100644 index 0000000..b96a558 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/IndexHeader.java @@ -0,0 +1,7 @@ +package net.northking.cctp.scriptcase.tools.hapUtil.resources; + +public class IndexHeader { + String version; + int fileSize; + int limitKeyConfigSize; +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyParam.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyParam.java new file mode 100644 index 0000000..a6ec55a --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyParam.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyType.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyType.java new file mode 100644 index 0000000..1c194e9 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/KeyType.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/LimitKeyConfig.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/LimitKeyConfig.java new file mode 100644 index 0000000..45e8a46 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/LimitKeyConfig.java @@ -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; +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/RecordItem.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/RecordItem.java new file mode 100644 index 0000000..349b6a5 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/RecordItem.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResType.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResType.java new file mode 100644 index 0000000..dba7c6b --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResType.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResourcesIndex.java b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResourcesIndex.java new file mode 100644 index 0000000..1db6197 --- /dev/null +++ b/cctp-atu/atu-script-case/src/main/java/net/northking/cctp/scriptcase/tools/hapUtil/resources/ResourcesIndex.java @@ -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; + } +} diff --git a/cctp-atu/atu-script-case/src/main/resources/mybatis/ext/AtuScriptInfo.Dao.xml b/cctp-atu/atu-script-case/src/main/resources/mybatis/ext/AtuScriptInfo.Dao.xml index ba1b384..64046a4 100644 --- a/cctp-atu/atu-script-case/src/main/resources/mybatis/ext/AtuScriptInfo.Dao.xml +++ b/cctp-atu/atu-script-case/src/main/resources/mybatis/ext/AtuScriptInfo.Dao.xml @@ -280,7 +280,7 @@ from where - script_type in ('3','4') + script_type in ('3','4','6') and id IN diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/AutomationWebSocketServer.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/AutomationWebSocketServer.java index 47c8de8..3f9ae65 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/AutomationWebSocketServer.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/AutomationWebSocketServer.java @@ -4,11 +4,15 @@ import com.alibaba.fastjson.JSON; import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest; import net.northking.cctp.upperComputer.automation.handler.AndroidAutomationHandler; import net.northking.cctp.upperComputer.automation.handler.AutomationMessageHandler; +import net.northking.cctp.upperComputer.automation.handler.HarmonyAutomationHandler; import net.northking.cctp.upperComputer.automation.handler.IosAutomationHandler; import net.northking.cctp.upperComputer.deviceManager.AndroidDeviceManager; +import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager; import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; import net.northking.cctp.upperComputer.deviceManager.common.Python3; import net.northking.cctp.upperComputer.driver.adb.AdbDevice; +import net.northking.cctp.upperComputer.driver.harmony.HarmonyDevice; +import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCConnectStatus; import net.northking.cctp.upperComputer.entity.PhoneEntity; import net.northking.cctp.upperComputer.service.DeviceConnectionService; import net.northking.cctp.upperComputer.utils.SpringUtils; @@ -72,6 +76,11 @@ public class AutomationWebSocketServer { return; } logger.info("收到建立手机【{}】的自动化连接", serial); + boolean deviceIsOnline = checkMobileIsOnline(serial, type); + if (!deviceIsOnline) { + closeSession(session, "当前设备已经离线,无法创建自动化连接"); + return; + } boolean success = prepareEnvironment(serial,type,session); if (!success) { closeSession(session, "设备自动化连接建立失败,请检查设备是否正常"); @@ -89,6 +98,8 @@ public class AutomationWebSocketServer { return AndroidDeviceManager.getInstance().checkMobileIsOnline(serial); } else if ("ios".equals(type)){ return IOSDeviceManager.getInstance().checkMobileIsOnline(serial); + }else if ("harmony".equals(type)){ + return HarmonyDeviceManager.getInstance().checkMobileIsOnline(serial); } return false; } @@ -186,6 +197,15 @@ public class AutomationWebSocketServer { this.handler = new IosAutomationHandler(phoneEntity,session); logger.info("手机[{}]初始化环境完成。。。。。。。", serial); return true; + }else if ("harmony".equals(type)) { + HarmonyDevice harmonyDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(serial); + if (harmonyDevice == null || harmonyDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED) { + logger.warn("当前设备【{}】不在线",serial); + return false; + } + this.deviceId = serial; + this.handler = new HarmonyAutomationHandler(session,harmonyDevice); + return true; } return false; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/constants/Command.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/constants/Command.java index db2845c..35974e5 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/constants/Command.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/constants/Command.java @@ -5,8 +5,8 @@ package net.northking.cctp.upperComputer.automation.constants; * @date : 2024/5/13 16:48 */ public enum Command { - CLEAR("清空文本",20); - + CLEAR("清空文本",20), + CLICK("点击", 21); private String name; private int code; diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/entity/ScreenInfo.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/entity/ScreenInfo.java new file mode 100644 index 0000000..8b032f0 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/entity/ScreenInfo.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AbstractAutomationHandler.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AbstractAutomationHandler.java index 6e88719..142374f 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AbstractAutomationHandler.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AbstractAutomationHandler.java @@ -1,14 +1,37 @@ package net.northking.cctp.upperComputer.automation.handler; +import cn.hutool.core.codec.Base64; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import net.northking.cctp.upperComputer.automation.constants.AutomationRequestCmd; +import net.northking.cctp.upperComputer.automation.constants.UpperParamKey; import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest; import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse; +import net.northking.cctp.upperComputer.automation.entity.ScreenInfo; +import net.northking.cctp.upperComputer.automation.utils.CloudTestOcrHelper; +import net.northking.cctp.upperComputer.automation.utils.OcrHelper; +import net.northking.cctp.upperComputer.config.HttpRequestPathConfig; +import net.northking.cctp.upperComputer.config.MobileProperty; +import net.northking.cctp.upperComputer.driver.ios.command.data.DragXYData; +import net.northking.cctp.upperComputer.driver.ios.command.data.TapXYData; +import net.northking.cctp.upperComputer.entity.Attachment; +import net.northking.cctp.upperComputer.exception.ExecuteException; +import net.northking.cctp.upperComputer.utils.HttpUtils; +import net.northking.cctp.upperComputer.utils.SpringUtils; +import net.northking.cctp.upperComputer.utils.deviceHepler.DeviceHelper; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.imageio.ImageIO; import javax.websocket.Session; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; public abstract class AbstractAutomationHandler implements AutomationMessageHandler{ @@ -19,6 +42,16 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand protected Session engineSession; + protected DeviceHelper deviceHandleHelper; + + protected ScreenInfo screenInfo; + + protected OcrHelper ocrHelper; + + public AbstractAutomationHandler() { + ocrHelper = new CloudTestOcrHelper(); + } + @Override public void automationHandle(CmdAutomationRequest request, Session engineSession) { String cmd = request.getCmd(); @@ -106,4 +139,351 @@ public abstract class AbstractAutomationHandler implements AutomationMessageHand } } + + protected Map getAppDetail(String appId) throws URISyntaxException { + Map 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 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 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 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 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 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 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); + } + } + } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AndroidAutomationHandler.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AndroidAutomationHandler.java index cf62e35..779b60c 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AndroidAutomationHandler.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/AndroidAutomationHandler.java @@ -28,6 +28,7 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{ private AndroidAgentSession syncAgentSession; //同步处理设备指令的AgentSession public AndroidAutomationHandler(AdbDevice currentDevice,Session session) throws IOException { + super(); this.adbDevice = currentDevice; this.serial = currentDevice.getSerial(); this.engineSession = session; @@ -170,6 +171,11 @@ public class AndroidAutomationHandler extends AbstractAutomationHandler{ } + @Override + public void initScreenData() { + + } + @Override public void pressHomeKey(CmdAutomationRequest request) { diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/HarmonyAutomationHandler.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/HarmonyAutomationHandler.java new file mode 100644 index 0000000..f4a9e01 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/HarmonyAutomationHandler.java @@ -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 initData = request.getData(); + boolean reInstall = false; + if (null != initData.get("forceReInstall")) { + reInstall = (boolean) initData.get("forceReInstall"); + } + String appId = (String) initData.get("appId"); + Map 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/IosAutomationHandler.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/IosAutomationHandler.java index eea7d6b..2c878f7 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/IosAutomationHandler.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/handler/IosAutomationHandler.java @@ -9,6 +9,7 @@ import net.northking.cctp.upperComputer.automation.constants.Command; import net.northking.cctp.upperComputer.automation.constants.UpperParamKey; import net.northking.cctp.upperComputer.automation.entity.CmdAutomationRequest; import net.northking.cctp.upperComputer.automation.entity.CmdAutomationResponse; +import net.northking.cctp.upperComputer.automation.entity.ScreenInfo; import net.northking.cctp.upperComputer.constants.HandCommand; import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager; @@ -23,10 +24,10 @@ import net.northking.cctp.upperComputer.exception.ExecuteException; import net.northking.cctp.upperComputer.service.IosDebuggerServiceImpl; import net.northking.cctp.upperComputer.utils.HttpUtils; import net.northking.cctp.upperComputer.utils.SpringUtils; +import net.northking.cctp.upperComputer.utils.deviceHepler.ios.IosDeviceHandleHelper; +import net.northking.cctp.upperComputer.utils.deviceHepler.ios.LinuxAndWindowsIosHandleHelper; +import net.northking.cctp.upperComputer.utils.deviceHepler.ios.MacIosHandleHelper; import net.northking.cctp.upperComputer.utils.hzBank.HzBankOcrUtils; -import net.northking.cctp.upperComputer.utils.ios.IosDeviceHandleHelper; -import net.northking.cctp.upperComputer.utils.ios.LinuxAndWindowsIosHandleHelper; -import net.northking.cctp.upperComputer.utils.ios.MacIosHandleHelper; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,9 +60,8 @@ public class IosAutomationHandler extends AbstractAutomationHandler { private PhoneEntity phoneEntity; - private IosDeviceHandleHelper deviceHandleHelper; - public IosAutomationHandler(PhoneEntity phoneEntity, Session session) { + super(); this.phoneEntity = phoneEntity; this.serial = phoneEntity.getUdid(); this.engineSession = session; @@ -572,31 +572,9 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } } - private String getOcrAreaToBase64(CmdAutomationRequest request) { - Map 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 data = request.getData(); - String ocrText = (String) data.get(UpperParamKey.OCR_TEXT); - logger.debug("拿到的截图参数----->>>>>查找的文本:{}", ocrText); -// File file = ScreenShotUtils.getIOSMobileScreenShot(serial); - File file = deviceHandleHelper.getScreenShotFile(serial); - logger.debug("执行步骤token:{},ocr截图:{}", request.getStepToken(), file.getAbsolutePath()); - return getFileBase64(file); - } + + @Override public void getVerificationCodeByNode(CmdAutomationRequest request) { @@ -624,8 +602,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler { if (uiNodeData.getX() <= 0 && uiNodeData.getY() <= 0) { response = CmdAutomationResponse.builderFailure(request, "元素不存在"); } else { - File shotFile = deviceHandleHelper.getScreenShotFile(serial, uiNodeData.getX(), uiNodeData.getY(), uiNodeData.getWidth(), uiNodeData.getHeight(), phoneEntity.getScreenWidth(), phoneEntity.getScreenHeight()); - String targetBase64 = getFileBase64(shotFile); + int x = uiNodeData.getX() * phoneEntity.getScale(); + int y = uiNodeData.getY() * phoneEntity.getScale(); + int width = uiNodeData.getWidth() * phoneEntity.getScale(); + int height = uiNodeData.getHeight() * phoneEntity.getScale(); + File screenShotFile = deviceHandleHelper.getScreenShotFile(phoneEntity.getUdid(), x, y, width, height); + String targetBase64 = getFileBase64(screenShotFile); if (StringUtils.isBlank(targetBase64)) { response = CmdAutomationResponse.builderFailure(request, "未获取到ocr区域"); return; @@ -939,46 +921,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } } - private TapXYData queryPoint(CmdAutomationRequest request) { - TapXYData tapXYData = null; - Map data = request.getData(); - Integer x = (Integer) data.get(UpperParamKey.X); - Integer y = (Integer) data.get(UpperParamKey.Y); - Integer screenWidth = (Integer) data.get(UpperParamKey.SCREEN_WIDTH); - Integer screenHeight = (Integer) data.get(UpperParamKey.SCREEN_HEIGHT); - boolean overTurn = false; - if (screenWidth > screenHeight) { - overTurn = true; - } - int width = phoneEntity.getScreenWidth()* phoneEntity.getScale(); - int height = phoneEntity.getScreenHeight()*phoneEntity.getScale(); - int realityWidth = width; - int realityHeight = height; - if (overTurn) { //宽高反了的情况,调换 - if (width < height) { - realityWidth = height; - realityHeight = width; - } - } else { - if (width > height) { - realityWidth = height; - realityHeight = width; - } - } - Double realityX = x.doubleValue() / screenWidth.doubleValue() * realityWidth; - Double realityY = y.doubleValue() / screenHeight.doubleValue() * realityHeight; - if (realityX <= 0 || realityX >= realityWidth || realityY <= 0 || realityY >= realityHeight) { - logger.warn("步骤id【{}】坐标方式超出了屏幕范围", request.getStepToken()); - } - int clickTrueX = new Double(realityX / phoneEntity.getScale()).intValue(); - int clickTrueY = new Double(realityY / phoneEntity.getScale()).intValue(); - tapXYData = new TapXYData(); - tapXYData.setX(clickTrueX); - tapXYData.setY(clickTrueY); - logger.debug("实际点击的位置------->>>>>>x:{},y:{}", clickTrueX, clickTrueY); - return tapXYData; - } - @Override public void handleNodeByOcr(CmdAutomationRequest request) { CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "根据ocr点击控件失败").withData(false); @@ -1425,10 +1367,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler { return allPoint.get(index - 1); } - private String getFileBase64(File file) { - String encode = Base64.encode(file); - return encode; - } @Override public void handleNodeByImage(CmdAutomationRequest request) { @@ -1722,6 +1660,14 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } } + @Override + public void initScreenData() { + this.screenInfo = new ScreenInfo(); + this.screenInfo.setWidth(this.phoneEntity.getScreenWidth() * this.phoneEntity.getScale()); + this.screenInfo.setHeight(this.phoneEntity.getScreenHeight() * this.phoneEntity.getScale()); + this.screenInfo.setScale(this.phoneEntity.getScale()); + } + @Override public void pressHomeKey(CmdAutomationRequest request) { CmdAutomationResponse response = CmdAutomationResponse.builderFailure(request, "点击home键失败").withData(false); @@ -1889,42 +1835,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } - /** - * 给截图增加红点操作 - * @param originFile - * @param x - * @param y - * @return - */ - private File addSignatureToImage(File originFile, Integer x, Integer y) { - if (x == null || y == null) { - return originFile; - } - if (!originFile.exists()) { - logger.info("截图文件不存在:{}", originFile.getAbsolutePath()); - return originFile; - } - String fullName = originFile.getName(); - String suffix = fullName.substring(fullName.lastIndexOf(".") + 1); - String name = fullName.substring(0, fullName.lastIndexOf(".")); - File targetFile = new File(originFile.getParentFile().getAbsolutePath() + File.separator + name + "_with_dot" + "." + suffix); - try { - BufferedImage bi = ImageIO.read(originFile); - Graphics2D g2d = bi.createGraphics(); // 生成画布 - g2d.setColor(Color.RED); // 红色 - g2d.fillOval(x, y, 20, 20); // 在图片的x,y上画上一个直径20的实心原点 - g2d.dispose(); - ImageIO.write(bi, suffix, targetFile); - } catch (IOException e) { - logger.error("io异常", e); - } catch (Exception e) { - logger.error("添加红点失败", e); - } - if (!targetFile.exists()) { - targetFile = originFile; - } - return targetFile; - } + @Override public void getElementValueByPathOcr(CmdAutomationRequest request) { @@ -2339,14 +2250,12 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } private String getOcrAreaByBodeToBase64(UiNodeData uiNodeData) { - Integer x = uiNodeData.getX(); - Integer y = uiNodeData.getY(); - Integer screenWidth = phoneEntity.getScreenWidth(); - Integer screenHeight = phoneEntity.getScreenHeight(); - Integer cutWidth = uiNodeData.getWidth(); - Integer cutHeight = uiNodeData.getHeight(); - logger.debug("拿到的截图参数----->>>>>x:{},y:{},screenWidth:{},screenHeight:{},cutWidth:{},cutHeight:{}", x, y, screenWidth, screenHeight, cutWidth, cutHeight); - File file = deviceHandleHelper.getScreenShotFile(serial, x, y, cutWidth, cutHeight, screenWidth, screenHeight); + int x = uiNodeData.getX() * phoneEntity.getScale(); + int y = uiNodeData.getY() * phoneEntity.getScale(); + int width = uiNodeData.getWidth() * phoneEntity.getScale(); + int height = uiNodeData.getHeight() * phoneEntity.getScale(); + logger.debug("拿到的截图参数----->>>>>x:{},y:{},cutWidth:{},cutHeight:{}", x, y, width, height); + File file = deviceHandleHelper.getScreenShotFile(phoneEntity.getUdid(), x, y, width, height); logger.debug("ocr截图:{}", file.getAbsolutePath()); String base64Str = getFileBase64(file); return base64Str; @@ -2604,75 +2513,6 @@ public class IosAutomationHandler extends AbstractAutomationHandler { } } - private TapXYData queryImageArea(CmdAutomationRequest request) { - TapXYData result = null; - Map 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 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 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 httpEntity = new HttpEntity<>(param, headers); - try { - URI uri = new URI(url); - Map body = HttpUtils.doPost(uri, httpEntity, Map.class); - Map bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap(); - logger.debug("以图找图返回结果:{}", JSON.toJSONString(body)); - if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) { - resultPoint = (Map) bodyMap.get("data"); - } - } catch (URISyntaxException e) { - logger.error("找图像地址有误,:{}", e); - } catch (Exception e) { - logger.error("请求图像解析服务失败,参数:{},错误:{}", JSON.toJSONString(param), e); - } - if (null != resultPoint) { - logger.info("步骤【{}】找到区域位置为:{}", request.getStepToken(), JSON.toJSONString(resultPoint)); - try { - byte[] bytes = new BASE64Decoder().decodeBuffer(targetBase64); - BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes)); - Integer width = read.getWidth(); - Integer height = read.getHeight(); - int clickTrueX = new Double((resultPoint.get("x").doubleValue() + resultPoint.get("w").doubleValue() / 2) / phoneEntity.getScale()).intValue(); - int clickTrueY = new Double((resultPoint.get("y").doubleValue() + resultPoint.get("h").doubleValue() / 2) / phoneEntity.getScale()).intValue(); - result = new TapXYData(); - result.setX(clickTrueX); - result.setY(clickTrueY); - } catch (IOException e) { - logger.error("转换base64图片失败:{}", e); - } - } else { - logger.warn("步骤【{}】未找到对应的图像", request.getStepToken()); - } - } else { - logger.warn("步骤【{}】下载的图像或者截图为空...............", request.getStepToken()); - } - return result; - } - private NKAgent getNkAgent() { int times = 0; NKAgent nkAgent = null; @@ -2705,45 +2545,7 @@ public class IosAutomationHandler extends AbstractAutomationHandler { return nkAgent; } - private Map getAppDetail(String appId) throws URISyntaxException { - Map 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; - } + + } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/CloudTestOcrHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/CloudTestOcrHelper.java new file mode 100644 index 0000000..a765777 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/CloudTestOcrHelper.java @@ -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 ocrParamMap = new HashMap<>(); + ocrParamMap.put("img_base64", base64Str); + ocrParamMap.put("targets", ocrText); + ocrParamMap.put("diffussion", diffussion != null ? diffussion : 1); + HttpEntity> 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 resultPoint = null; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + Map 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 httpEntity = new HttpEntity<>(param, headers); + try { + URI uri = new URI(ocrImgFindArea); + Map body = HttpUtils.doPost(uri, httpEntity, Map.class); + Map bodyMap = JSONObject.parseObject(JSON.toJSONString(body)).getInnerMap(); + if (Boolean.parseBoolean(String.valueOf(bodyMap.get("success")))) { + resultPoint = (Map) 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 ocrParamMap = new HashMap<>(); + ocrParamMap.put("img_base64", targetBase64); + ocrParamMap.put("targets", ""); + HttpEntity 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 ocrParamMap = new HashMap<>(); + ocrParamMap.put("img_base64", base64); + ocrParamMap.put("type", codeType); + HttpEntity> 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 paramMap = new HashMap<>(); + paramMap.put("img_base64", ocrArea); + HttpEntity 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 ocrParamMap = new HashMap<>(); + ocrParamMap.put("image_base64", imgBase64); + ocrParamMap.put("targets", text); + ocrParamMap.put("direction", direction); + ocrParamMap.put("n", index); + HttpEntity 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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/OcrHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/OcrHelper.java new file mode 100644 index 0000000..abf68f6 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/automation/utils/OcrHelper.java @@ -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); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/config/HttpRequestPathConfig.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/config/HttpRequestPathConfig.java index 3ad28bb..a232433 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/config/HttpRequestPathConfig.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/config/HttpRequestPathConfig.java @@ -25,6 +25,38 @@ public class HttpRequestPathConfig { private String ocrGetAllTextNum; + private String matchingThreshold; //图像匹配阈值 + + private String ocrGetTableColValue; //ocr获取表格列内容 + + private String ocrGetTextDirectionText; //ocr识别指定文字指定方向的内容 + + + public String getOcrGetTextDirectionText() { + return ocrGetTextDirectionText; + } + + public void setOcrGetTextDirectionText(String ocrGetTextDirectionText) { + this.ocrGetTextDirectionText = ocrGetTextDirectionText; + } + + + public String getMatchingThreshold() { + return matchingThreshold; + } + + public void setMatchingThreshold(String matchingThreshold) { + this.matchingThreshold = matchingThreshold; + } + + public String getOcrGetTableColValue() { + return ocrGetTableColValue; + } + + public void setOcrGetTableColValue(String ocrGetTableColValue) { + this.ocrGetTableColValue = ocrGetTableColValue; + } + public String getOcrGetAllTextNum() { return ocrGetAllTextNum; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/constants/HarmonyKeyBoardCodeEnum.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/constants/HarmonyKeyBoardCodeEnum.java new file mode 100644 index 0000000..404196f --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/constants/HarmonyKeyBoardCodeEnum.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AbstractDeviceManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AbstractDeviceManager.java index f41ad40..d59d931 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AbstractDeviceManager.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AbstractDeviceManager.java @@ -1,6 +1,7 @@ package net.northking.cctp.upperComputer.deviceManager; import com.alibaba.fastjson.JSON; +import net.northking.cctp.upperComputer.automation.entity.ScreenInfo; import net.northking.cctp.upperComputer.entity.CdDeviceModel; import net.northking.cctp.upperComputer.entity.CdDeviceRegisterDto; import net.northking.cctp.upperComputer.entity.CdMobileBrand; @@ -23,6 +24,7 @@ import java.net.URI; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * @author : yineng.huang @@ -32,6 +34,8 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana private Logger logger = LoggerFactory.getLogger(AbstractDeviceManager.class); + private ConcurrentHashMap screenInfoMap = new ConcurrentHashMap<>(); + @Override public void publishDeviceAgentAlready(String serial) { SpringUtils.getBean(DeviceConnectionService.class).deviceAgentAlready(serial); @@ -190,4 +194,9 @@ public abstract class AbstractDeviceManager extends Thread implements DeviceMana public void offlineDevice(String serial) { } + + @Override + public void setScreenInfo(String deviceId, ScreenInfo screenInfo) { + this.screenInfoMap.put(deviceId, screenInfo); + } } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AndroidDeviceManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AndroidDeviceManager.java index f9509ad..a10ba62 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AndroidDeviceManager.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/AndroidDeviceManager.java @@ -211,6 +211,7 @@ public class AndroidDeviceManager extends AbstractDeviceManager { } + @Override public boolean checkMobileIsOnline(String serial) { AndroidDeviceInitThread initThread = onlineDeviceInitMap.get(serial); if (null != initThread && !initThread.isInterrupted() && initThread.isAlive()) { diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/DeviceManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/DeviceManager.java index 964cf6a..6953e81 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/DeviceManager.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/DeviceManager.java @@ -1,5 +1,7 @@ package net.northking.cctp.upperComputer.deviceManager; +import net.northking.cctp.upperComputer.automation.entity.ScreenInfo; + import java.util.LinkedHashMap; import java.util.List; @@ -54,4 +56,9 @@ public interface DeviceManager { * @param serial */ void offlineDevice(String serial); + + public boolean checkMobileIsOnline(String serial); + + + public void setScreenInfo(String deviceId, ScreenInfo screenInfo); } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/HarmonyDeviceManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/HarmonyDeviceManager.java new file mode 100644 index 0000000..a5e10fb --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/HarmonyDeviceManager.java @@ -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; + +/** + * 鸿蒙设备管理器 + *
+ * 因鸿蒙hdc的API并未完全解析完毕,因此仅支持连接本机的设备 + */ +public class HarmonyDeviceManager extends AbstractDeviceManager { + + private static final Logger log = LoggerFactory.getLogger(HarmonyDeviceManager.class); + + private HarmonyDeviceListener harmonyDeviceListener = new HarmonyDeviceListener(); + + private ConcurrentHashMap 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 AGENT_ABI_MAP = new ConcurrentHashMap<>(); + + /** + * 当前系统设备列表 + */ + private final ArrayList deviceList = new ArrayList<>(); + + /** + * Harmony设备初始化线程 + */ + private ConcurrentHashMap 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"); + } + + /** + * 设置临时工作目录 + *
+ * 如果有需求,应该在使用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 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 getOnlineDevice() { + Enumeration keys = onlineDeviceInitMap.keys(); + List 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 entry : onlineDeviceInitMap.entrySet()) { + HarmonyProvider provider = entry.getValue(); + provider.exitDeviceInit(); + } + onlineDeviceInitMap.clear(); + } + //todo:设备和投屏 + } + + public void handleHarmonyDevice(ArrayList currentDevices) { + ArrayList ableDevice = new ArrayList(); + ArrayList lostDevice = new ArrayList(); + 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); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/IOSDeviceManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/IOSDeviceManager.java index 44e115a..f46e67c 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/IOSDeviceManager.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/IOSDeviceManager.java @@ -288,6 +288,7 @@ public class IOSDeviceManager extends AbstractDeviceManager { interrupt(); } + @Override public boolean checkMobileIsOnline(String serial) { PhoneEntity entity = this.phoneMap.get(serial); if (null != entity && entity.getStatus()) { diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/UpperComputerManager.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/UpperComputerManager.java index 91876dd..8de1aec 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/UpperComputerManager.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/UpperComputerManager.java @@ -70,8 +70,11 @@ public class UpperComputerManager { List onlineAndroidDeviceIds = AndroidDeviceManager.getInstance().getOnlineDevice(); //ios List onlineIosDeviceIds = IOSDeviceManager.getInstance().getOnlineDevice(); + //harmony + List onlineHarmonyDeviceIds = HarmonyDeviceManager.getInstance().getOnlineDevice(); onlineDeviceIds.addAll(onlineAndroidDeviceIds); onlineDeviceIds.addAll(onlineIosDeviceIds); + onlineDeviceIds.addAll(onlineHarmonyDeviceIds); this.computerHeartInfo.setDeviceIds(onlineDeviceIds); return this.computerHeartInfo; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/listener/HarmonyDeviceListener.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/listener/HarmonyDeviceListener.java new file mode 100644 index 0000000..de5c329 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/listener/HarmonyDeviceListener.java @@ -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 currentDevices) { + HarmonyDeviceManager.getInstance().handleHarmonyDevice(currentDevices); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/screen/HarmonyScreenResponseThread.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/screen/HarmonyScreenResponseThread.java new file mode 100644 index 0000000..4c70061 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/screen/HarmonyScreenResponseThread.java @@ -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 iterator = webSessions.iterator(); + while (iterator.hasNext()) { + Session webSession = iterator.next(); + if (webSession.isOpen()) { + if (sendDeviceStatus) { + Map 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); + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/HarmonyProvider.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/HarmonyProvider.java new file mode 100644 index 0000000..1be02d7 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/HarmonyProvider.java @@ -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; + +/** + * 鸿蒙设备调用提供器 + *
+ * 如果使用此对象进行设备操作,则所有操作均通过此对象进行(对象有提供的),否则会发生状态管理冲突。 + *
+ * 当设备发生不可用时,对外隐藏恢复过程。 + */ + +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 requestJobList = new ArrayList<>(); + + private StatusListener statusListener = null; + + /** + * 当前屏幕图片流数据捕捉回调 + *
+ * 不为null时,标志着当前处于需要开启屏幕流的状态,当设备从不可用恢复时,需要自动重新开始屏幕流 + */ + private HyppiumAgent.OnCaptureData screenCaptureDataCallback = null; + + /** + * 屏幕图片流缩放率 + */ + private float screenCaptureScale = 1.0f; + + /** + * 当前UI事件数据捕捉回调 + *
+ * 不为null时,标志着当前处于需要开启UI事件捕捉的状态,当设备从不可用恢复时,需要自动重新开始UI事件数据捕捉 + */ + private HyppiumAgent.OnCaptureData uiActionCaptureDataCallback = null; + + private LinkedHashMap 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(); + } + + /** + * 设置状态监听器 + *
+ * 用于知道当前是否可用 + * + * @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动作 + *
+ * 会得到动作事件数据,以及一些事件中的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 scopeRequest(DoSessionRequestBlock block, Class 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 getDeviceInfo() throws IOException { + LinkedHashMap 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 request(); + } + + /** + * 放弃当前进行中的请求 + *
+ * 请求方法会立即返回结果,实际请求将会等待至请求超时后结束(放弃后下一次可能因此不会马上开始) + */ + 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); + } + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/InitDevice.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/InitDevice.java new file mode 100644 index 0000000..44c0737 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/InitDevice.java @@ -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 getDeviceInfo() throws IOException; + + public void exitDeviceInit(); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IIOJob.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IIOJob.java new file mode 100644 index 0000000..3dc6e4b --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IIOJob.java @@ -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); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOJob.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOJob.java new file mode 100644 index 0000000..dabf8c8 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOJob.java @@ -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读取工作
+ * 获取和控制单个Socket读取工作情况 + */ +public class IOJob implements IIOJob, Runnable { + private final InputStream inputStream; + + private final ThreadScope handleScope; + + private final OnReadBufferCallback 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 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 callback) { + this.socket = null; + this.inputStream = inputStream; + this.handleScope = handleScope; + this.callback = callback; + readThread.start(); + } + + + public ThreadScope getHandleScope() { + return handleScope; + } + + public OnReadBufferCallback getCallback() { + return callback; + } + + /** + * 标记byteBuffer中的数据已经被处理完毕了,新的数据可以进入byteBuffer + */ + private void markReadDone() { + synchronized (waitDoReadLock) { + waitDoReadLock.notifyAll(); + } + } + + + /** + * 标记此读取工作已经结束
+ * 在稍后的时机将会被IOScope释放 + */ + public void markFinish() { + finish = true; + if (callback instanceof BufferToLineReader) { + handleScope.launch((job) -> { + ((BufferToLineReader) 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; + } + + /** + * 数据读取回调
+ * onRead方法将在调用的ThreadScope中运行 + */ + public interface OnReadBufferCallback { + /** + * 单词读取操作完成 + * + * @param buffer 读取到的数据buffer,如果为null,则读取失败,检查异常 + * @param e 读取过程发生的异常,null则为一切正常 + */ + void onRead(IIOJob job, ByteBuffer buffer, Exception e); + } + + /** + * 行数据读取回调
+ * 以文本方式读取内容,当读取到换行“\n”时,进行回调。
+ * 若结束读取时仍缓冲有内容但是行并未结束,也会作为单行进行回调。 + */ + public interface OnReadLineCallback { + /** + * 读取到新的行 + * + * @param job 当前job + * @param line 行内容,不包括换行符 + * @param e 如果读取过程中发生异常,通过此获取,如果一切正常,此处为null + */ + void onLine(JOB job, String line, Exception e); + } + + /** + * 将缓冲区内容按行读取,并通过回调返回
+ * 本类逻辑将在指定的ThreadScope执行,并不在IOScope中
+ * 读取的内容会去除首位不可见字符 + */ + public static class BufferToLineReader implements OnReadBufferCallback { + + private final ByteArrayOutputStream strOutputStream = new ByteArrayOutputStream(BUFFER_SIZE); + + private String holdString = null; + + private final OnReadLineCallback callback; + + public BufferToLineReader(OnReadLineCallback 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; + } + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOScope.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOScope.java new file mode 100644 index 0000000..13b7da4 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/IOScope.java @@ -0,0 +1,9 @@ +package net.northking.cctp.upperComputer.deviceManager.thread.util; + +/** + * IO块
+ * 使用单一线程处理多个IO请求 + */ +public class IOScope { + public static final int BUFFER_SIZE = 163840; +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/LaunchBlock.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/LaunchBlock.java new file mode 100644 index 0000000..a867d66 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/LaunchBlock.java @@ -0,0 +1,9 @@ +package net.northking.cctp.upperComputer.deviceManager.thread.util; + +/** + * 交给线程块运行的代码块
+ * 代码块将在合适的时候执行 + */ +public interface LaunchBlock { + void run(ThreadJob job); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadJob.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadJob.java new file mode 100644 index 0000000..4d46e53 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadJob.java @@ -0,0 +1,124 @@ +package net.northking.cctp.upperComputer.deviceManager.thread.util; + +/** + * 线程工作
+ * 产生与线程块的待执行任务工作 + */ +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(); + } + } + + /** + * 是否有效
+ * 无效的任务将不被执行 + * + * @return 有效 + */ + public boolean isValid() { + return valid; + } + + /** + * 取消工作 + * 取消后工作将无效,等待任务结束的线程将会被立即唤醒 + */ + public void cancel() { + valid = false; + synchronized (lock) { + lock.notifyAll(); + } + } + + /** + * 是否保持运行
+ * 如果逻辑中需要判断是否应该继续而不是被取消工作,使用此方法判断 + * + * @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 返回对象的类型 + * @return 结果,如果对象类型与结果类型不一致,也会返回null + */ + public T getResult(Class clazz) { + if (result == null) return null; + if (result.getClass() != clazz) { + return null; + } + //noinspection unchecked + return (T) result; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadScope.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadScope.java new file mode 100644 index 0000000..0f5df0f --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/deviceManager/thread/util/ThreadScope.java @@ -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; + +/** + * 线程块
+ * 在块内的代码将在指定线程执行,提交执行的代码可取消或等待至结束完毕。 + */ +public class ThreadScope implements Runnable { + + private static final Logger log = LoggerFactory.getLogger(ThreadScope.class); + /** + * 默认ThreadScope
+ * 为跨项目准备的
+ * 不要进行depose操作,提交的逻辑不应有死循环,并尽可能的耗时短 + */ + public static final ThreadScope Default = new ThreadScope("default"); + + /** + * 线程块名称 + */ + private final String name; + + /** + * 线程 + */ + private final Thread thread = new Thread(this); + + /** + * 工作队列 + */ + private final LinkedBlockingDeque jobQueue = new LinkedBlockingDeque<>(); + + private final LinkedList childScopes = new LinkedList<>(); + + private final ThreadScope parentScope; + + /** + * 是否已经被废弃 + */ + private boolean deposed = false; + + /** + * 持有的ioJob + */ + private final ArrayList 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; + } + + /** + * 释放线程块
+ * 取消所有待执行的任务,正在执行的任务将收到中断异常。 + * 所有子线程块也将被释放 + */ + 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 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."); + } + + /** + * 创建子线程块
+ * 每个子线程块将运行于各自独立的线程
+ * 父级线程块释放时,所有子线程块也将全部释放。 + */ + 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,并在此线程块中处理读取结果
+ * @param socket 要读取的Socket + * @param callback 读取内容回调 + * @return 读取job + * @throws IOException 开始读取时可能发生的IO异常 + */ + public IOJob readSocketBuffer(Socket socket, IOJob.OnReadBufferCallback callback) throws IOException { + IOJob ioJob = new IOJob(socket, this, callback); + synchronized (ioJobs) { + ioJobs.add(ioJob); + } + return ioJob; + } + + /** + * 异步读取InputStream(不要用来读取网络Socket),并在此线程块中处理读取结果
+ * @param inputStream 要读取的InputStream + * @param callback 读取内容回调 + * @return 读取job + * @throws IOException 开始读取时可能发生的IO异常 + */ + public IOJob readSocketBuffer(InputStream inputStream, IOJob.OnReadBufferCallback 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 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 callback) throws IOException { + IOJob.BufferToLineReader reader = new IOJob.BufferToLineReader<>(callback); + return readSocketBuffer(inputStream, reader); + } + + protected void ioJobFinish(IOJob ioJob) { + synchronized (ioJobs) { + ioJobs.remove(ioJob); + } + } + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/HarmonyDevice.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/HarmonyDevice.java new file mode 100644 index 0000000..11a3492 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/HarmonyDevice.java @@ -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 + *
+ * 启用后就可以执行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; + } + + /** + * 启用隐藏的测试模式 + *
+ * 启用后就可以执行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; + } + + /** + * 输入文本 + *
+ * 借助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; + } + + /** + * 按键 + *
+ * 自动按下并抬起,支持组合键 + *
+ * 借助shell的实现 + */ + public boolean keyEvent(List 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 getUiTree() { + ArrayList list = new ArrayList(); + 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>() { + }.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; + } + + /** + * 屏幕截图
+ * 获得一张原始分辨率的无损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; + } + + /** + * 屏幕截图
+ * 获得一张原始分辨率的无损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()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/DeviceActionResult.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/DeviceActionResult.java new file mode 100644 index 0000000..e46b148 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/DeviceActionResult.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/ForwardRule.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/ForwardRule.java new file mode 100644 index 0000000..51d03d8 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/ForwardRule.java @@ -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; + + /** + * 是否为反向转发。
+ * 请求从本机流向设备端内部,为正向
+ * 请求从设备端流向本机,为反向 + */ + 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"); + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectStatus.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectStatus.java new file mode 100644 index 0000000..c4a51a4 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectStatus.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectType.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectType.java new file mode 100644 index 0000000..9154cab --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCConnectType.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDevice.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDevice.java new file mode 100644 index 0000000..6d1a6d5 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDevice.java @@ -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 + '\'' + + '}'; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceListener.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceListener.java new file mode 100644 index 0000000..6e46c2f --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceListener.java @@ -0,0 +1,17 @@ +package net.northking.cctp.upperComputer.driver.harmony.hdc; + +import java.util.ArrayList; + +/** + * 设备变动监听接口 + *
+ * 当设备发生变动时,告知当前所有设备信息 + */ +public interface HDCDeviceListener { + /** + * 当设备变动后,此方法将会被调用 + * + * @param currentDevices 当前所有设备的信息 + */ + void onDeviceChanged(ArrayList currentDevices); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceObserver.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceObserver.java new file mode 100644 index 0000000..531ab41 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCDeviceObserver.java @@ -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设备的变化 + *
+ * 每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 lastDevices = new ArrayList<>(); + private final ArrayList 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 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设备监听退出................................"); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCSession.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCSession.java new file mode 100644 index 0000000..211d630 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HDCSession.java @@ -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文本指令 + *
+ * 会自动在文本末尾增加一个\0 + *
+ * 会检查会话是否有效 + * + * @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数据包 + *
+ * 由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数据包 + *
+ * 会自动先发送数据包长度,不适合shell + * + * @param data 要发生的数据本体 + * @throws IOException 发送时发生IO异常 + */ + public void sendHdcData(byte[] data) throws IOException { + sendHdcData(data, 0, data.length); + } + + /** + * 发送一个HDC数据包 + *
+ * 会自动先发送数据包长度,不适合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) { + } + } + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HarmonyKeyCode.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HarmonyKeyCode.java new file mode 100644 index 0000000..b1160ab --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/HarmonyKeyCode.java @@ -0,0 +1,1347 @@ +package net.northking.cctp.upperComputer.driver.harmony.hdc; + +/** + * 按键设备的键码值,按键设备包括键盘、电源键、拍照键等。 + * + * @link @ohos.multimodalInput.keyCode (键值) + */ +@SuppressWarnings("unused") +public class HarmonyKeyCode { + /** + * 功能(Fn)键 + **/ + public static final int KEYCODE_FN = 0; + /** + * 未知按键 + **/ + public static final int KEYCODE_UNKNOWN = -1; + /** + * 功能(Home)键 + **/ + public static final int KEYCODE_HOME = 1; + /** + * 返回键 + **/ + public static final int KEYCODE_BACK = 2; + /** + * 多媒体键:播放/暂停 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_PLAY_PAUSE = 10; + /** + * 多媒体键:停止 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_STOP = 11; + /** + * 多媒体键:下一首 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_NEXT = 12; + /** + * 多媒体键:上一首 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_PREVIOUS = 13; + /** + * 多媒体键:快退 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_REWIND = 14; + /** + * 多媒体键:快进 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_FAST_FORWARD = 15; + /** + * 音量增加键 + **/ + public static final int KEYCODE_VOLUME_UP = 16; + /** + * 音量减小键 + **/ + public static final int KEYCODE_VOLUME_DOWN = 17; + /** + * 电源键 + **/ + public static final int KEYCODE_POWER = 18; + /** + * 拍照键 + **/ + public static final int KEYCODE_CAMERA = 19; + /** + * 扬声器静音键 + **/ + public static final int KEYCODE_VOLUME_MUTE = 22; + /** + * 话筒静音键 + **/ + public static final int KEYCODE_MUTE = 23; + /** + * 亮度调节按键:调亮 + **/ + public static final int KEYCODE_BRIGHTNESS_UP = 40; + /** + * 亮度调节按键:调暗 + **/ + public static final int KEYCODE_BRIGHTNESS_DOWN = 41; + /** + * 按键'0' + **/ + public static final int KEYCODE_0 = 2000; + /** + * 按键'1' + **/ + public static final int KEYCODE_1 = 2001; + /** + * 按键'2' + **/ + public static final int KEYCODE_2 = 2002; + /** + * 按键'3' + **/ + public static final int KEYCODE_3 = 2003; + /** + * 按键'4' + **/ + public static final int KEYCODE_4 = 2004; + /** + * 按键'5' + **/ + public static final int KEYCODE_5 = 2005; + /** + * 按键'6' + **/ + public static final int KEYCODE_6 = 2006; + /** + * 按键'7' + **/ + public static final int KEYCODE_7 = 2007; + /** + * 按键'8' + **/ + public static final int KEYCODE_8 = 2008; + /** + * 按键'9' + **/ + public static final int KEYCODE_9 = 2009; + /** + * 按键'*' + **/ + public static final int KEYCODE_STAR = 2010; + /** + * 按键'#' + **/ + public static final int KEYCODE_POUND = 2011; + /** + * 导航键:向上 + **/ + public static final int KEYCODE_DPAD_UP = 2012; + /** + * 导航键:向下 + **/ + public static final int KEYCODE_DPAD_DOWN = 2013; + /** + * 导航键:向左 + **/ + public static final int KEYCODE_DPAD_LEFT = 2014; + /** + * 导航键:向右 + **/ + public static final int KEYCODE_DPAD_RIGHT = 2015; + /** + * 导航键:确定键 + **/ + public static final int KEYCODE_DPAD_CENTER = 2016; + /** + * 按键'A' + **/ + public static final int KEYCODE_A = 2017; + /** + * 按键'B' + **/ + public static final int KEYCODE_B = 2018; + /** + * 按键'C' + **/ + public static final int KEYCODE_C = 2019; + /** + * 按键'D' + **/ + public static final int KEYCODE_D = 2020; + /** + * 按键'E' + **/ + public static final int KEYCODE_E = 2021; + /** + * 按键'F' + **/ + public static final int KEYCODE_F = 2022; + /** + * 按键'G' + **/ + public static final int KEYCODE_G = 2023; + /** + * 按键'H' + **/ + public static final int KEYCODE_H = 2024; + /** + * 按键'I' + **/ + public static final int KEYCODE_I = 2025; + /** + * 按键'J' + **/ + public static final int KEYCODE_J = 2026; + /** + * 按键'K' + **/ + public static final int KEYCODE_K = 2027; + /** + * 按键'L' + **/ + public static final int KEYCODE_L = 2028; + /** + * 按键'M' + **/ + public static final int KEYCODE_M = 2029; + /** + * 按键'N' + **/ + public static final int KEYCODE_N = 2030; + /** + * 按键'O' + **/ + public static final int KEYCODE_O = 2031; + /** + * 按键'P' + **/ + public static final int KEYCODE_P = 2032; + /** + * 按键'Q' + **/ + public static final int KEYCODE_Q = 2033; + /** + * 按键'R' + **/ + public static final int KEYCODE_R = 2034; + /** + * 按键'S' + **/ + public static final int KEYCODE_S = 2035; + /** + * 按键'T' + **/ + public static final int KEYCODE_T = 2036; + /** + * 按键'U' + **/ + public static final int KEYCODE_U = 2037; + /** + * 按键'V' + **/ + public static final int KEYCODE_V = 2038; + /** + * 按键'W' + **/ + public static final int KEYCODE_W = 2039; + /** + * 按键'X' + **/ + public static final int KEYCODE_X = 2040; + /** + * 按键'Y' + **/ + public static final int KEYCODE_Y = 2041; + /** + * 按键'Z' + **/ + public static final int KEYCODE_Z = 2042; + /** + * 按键',' + **/ + public static final int KEYCODE_COMMA = 2043; + /** + * 按键'.' + **/ + public static final int KEYCODE_PERIOD = 2044; + /** + * 左Alt键 + **/ + public static final int KEYCODE_ALT_LEFT = 2045; + /** + * 右Alt键 + **/ + public static final int KEYCODE_ALT_RIGHT = 2046; + /** + * 左Shift键 + **/ + public static final int KEYCODE_SHIFT_LEFT = 2047; + /** + * 右Shift键 + **/ + public static final int KEYCODE_SHIFT_RIGHT = 2048; + /** + * Tab键 + **/ + public static final int KEYCODE_TAB = 2049; + /** + * 空格键 + **/ + public static final int KEYCODE_SPACE = 2050; + /** + * 符号修改器按键 + **/ + public static final int KEYCODE_SYM = 2051; + /** + * 浏览器功能键,此键用于启动浏览器应用程序 + **/ + public static final int KEYCODE_EXPLORER = 2052; + /** + * 电子邮件功能键,此键用于启动电子邮件应用程序 + **/ + public static final int KEYCODE_ENVELOPE = 2053; + /** + * 回车键 + **/ + public static final int KEYCODE_ENTER = 2054; + /** + * 退格键 + **/ + public static final int KEYCODE_DEL = 2055; + /** + * 按键'`' + **/ + public static final int KEYCODE_GRAVE = 2056; + /** + * 按键'-' + **/ + public static final int KEYCODE_MINUS = 2057; + /** + * 按键'=' + **/ + public static final int KEYCODE_EQUALS = 2058; + /** + * 按键'[' + **/ + public static final int KEYCODE_LEFT_BRACKET = 2059; + /** + * 按键']' + **/ + public static final int KEYCODE_RIGHT_BRACKET = 2060; + /** + * 按键'\' + **/ + public static final int KEYCODE_BACKSLASH = 2061; + /** + * 按键';' + **/ + public static final int KEYCODE_SEMICOLON = 2062; + /** + * 按键''' (单引号) + **/ + public static final int KEYCODE_APOSTROPHE = 2063; + /** + * 按键'/' + **/ + public static final int KEYCODE_SLASH = 2064; + /** + * 按键'@' + **/ + public static final int KEYCODE_AT = 2065; + /** + * 按键'+' + **/ + public static final int KEYCODE_PLUS = 2066; + /** + * 菜单键 + **/ + public static final int KEYCODE_MENU = 2067; + /** + * 向上翻页键 + **/ + public static final int KEYCODE_PAGE_UP = 2068; + /** + * 向下翻页键 + **/ + public static final int KEYCODE_PAGE_DOWN = 2069; + /** + * ESC键 + **/ + public static final int KEYCODE_ESCAPE = 2070; + /** + * 删除键 + **/ + public static final int KEYCODE_FORWARD_DEL = 2071; + /** + * 左Ctrl键 + **/ + public static final int KEYCODE_CTRL_LEFT = 2072; + /** + * 右Ctrl键 + **/ + public static final int KEYCODE_CTRL_RIGHT = 2073; + /** + * 大写锁定键 + **/ + public static final int KEYCODE_CAPS_LOCK = 2074; + /** + * 滚动锁定键 + **/ + public static final int KEYCODE_SCROLL_LOCK = 2075; + /** + * 左元修改器键 + **/ + public static final int KEYCODE_META_LEFT = 2076; + /** + * 右元修改器键 + **/ + public static final int KEYCODE_META_RIGHT = 2077; + /** + * 功能键 + **/ + public static final int KEYCODE_FUNCTION = 2078; + /** + * 系统请求/打印屏幕键 + **/ + public static final int KEYCODE_SYSRQ = 2079; + /** + * Break/Pause键 + **/ + public static final int KEYCODE_BREAK = 2080; + /** + * 光标移动到开始键 + **/ + public static final int KEYCODE_MOVE_HOME = 2081; + /** + * 光标移动到末尾键 + **/ + public static final int KEYCODE_MOVE_END = 2082; + /** + * 插入键 + **/ + public static final int KEYCODE_INSERT = 2083; + /** + * 前进键 + **/ + public static final int KEYCODE_FORWARD = 2084; + /** + * 多媒体键:播放 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_PLAY = 2085; + /** + * 多媒体键:暂停 + *

+ * 元服务API: 从API version 12开始,该接口支持在元服务中使用。 + **/ + public static final int KEYCODE_MEDIA_PAUSE = 2086; + /** + * 多媒体键:关闭 + **/ + public static final int KEYCODE_MEDIA_CLOSE = 2087; + /** + * 多媒体键:弹出 + **/ + public static final int KEYCODE_MEDIA_EJECT = 2088; + /** + * 多媒体键:录音 + **/ + public static final int KEYCODE_MEDIA_RECORD = 2089; + /** + * 按键'F1' + **/ + public static final int KEYCODE_F1 = 2090; + /** + * 按键'F2' + **/ + public static final int KEYCODE_F2 = 2091; + /** + * 按键'F3' + **/ + public static final int KEYCODE_F3 = 2092; + /** + * 按键'F4' + **/ + public static final int KEYCODE_F4 = 2093; + /** + * 按键'F5' + **/ + public static final int KEYCODE_F5 = 2094; + /** + * 按键'F6' + **/ + public static final int KEYCODE_F6 = 2095; + /** + * 按键'F7' + **/ + public static final int KEYCODE_F7 = 2096; + /** + * 按键'F8' + **/ + public static final int KEYCODE_F8 = 2097; + /** + * 按键'F9' + **/ + public static final int KEYCODE_F9 = 2098; + /** + * 按键'F10' + **/ + public static final int KEYCODE_F10 = 2099; + /** + * 按键'F11' + **/ + public static final int KEYCODE_F11 = 2100; + /** + * 按键'F12' + **/ + public static final int KEYCODE_F12 = 2101; + /** + * 小键盘锁 + **/ + public static final int KEYCODE_NUM_LOCK = 2102; + /** + * 小键盘按键'0' + **/ + public static final int KEYCODE_NUMPAD_0 = 2103; + /** + * 小键盘按键'1' + **/ + public static final int KEYCODE_NUMPAD_1 = 2104; + /** + * 小键盘按键'2' + **/ + public static final int KEYCODE_NUMPAD_2 = 2105; + /** + * 小键盘按键'3' + **/ + public static final int KEYCODE_NUMPAD_3 = 2106; + /** + * 小键盘按键'4' + **/ + public static final int KEYCODE_NUMPAD_4 = 2107; + /** + * 小键盘按键'5' + **/ + public static final int KEYCODE_NUMPAD_5 = 2108; + /** + * 小键盘按键'6' + **/ + public static final int KEYCODE_NUMPAD_6 = 2109; + /** + * 小键盘按键'7' + **/ + public static final int KEYCODE_NUMPAD_7 = 2110; + /** + * 小键盘按键'8' + **/ + public static final int KEYCODE_NUMPAD_8 = 2111; + /** + * 小键盘按键'9' + **/ + public static final int KEYCODE_NUMPAD_9 = 2112; + /** + * 小键盘按键'/' + **/ + public static final int KEYCODE_NUMPAD_DIVIDE = 2113; + /** + * 小键盘按键'*' + **/ + public static final int KEYCODE_NUMPAD_MULTIPLY = 2114; + /** + * 小键盘按键'-' + **/ + public static final int KEYCODE_NUMPAD_SUBTRACT = 2115; + /** + * 小键盘按键'+' + **/ + public static final int KEYCODE_NUMPAD_ADD = 2116; + /** + * 小键盘按键'.' + **/ + public static final int KEYCODE_NUMPAD_DOT = 2117; + /** + * 小键盘按键',' + **/ + public static final int KEYCODE_NUMPAD_COMMA = 2118; + /** + * 小键盘按键回车 + **/ + public static final int KEYCODE_NUMPAD_ENTER = 2119; + /** + * 小键盘按键'=' + **/ + public static final int KEYCODE_NUMPAD_EQUALS = 2120; + /** + * 小键盘按键'(' + **/ + public static final int KEYCODE_NUMPAD_LEFT_PAREN = 2121; + /** + * 小键盘按键')' + **/ + public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 2122; + /** + * 虚拟多任务键 + **/ + public static final int KEYCODE_VIRTUAL_MULTITASK = 2210; + /** + * 睡眠键 + **/ + public static final int KEYCODE_SLEEP = 2600; + /** + * 日文全宽/半宽键 + **/ + public static final int KEYCODE_ZENKAKU_HANKAKU = 2601; + /** + * 102nd按键 + **/ + public static final int KEYCODE_102ND = 2602; + /** + * 日文Ro键 + **/ + public static final int KEYCODE_RO = 2603; + /** + * 日文片假名键 + **/ + public static final int KEYCODE_KATAKANA = 2604; + /** + * 日文平假名键 + **/ + public static final int KEYCODE_HIRAGANA = 2605; + /** + * 日文转换键 + **/ + public static final int KEYCODE_HENKAN = 2606; + /** + * 日语片假名/平假名键 + **/ + public static final int KEYCODE_KATAKANA_HIRAGANA = 2607; + /** + * 日文非转换键 + **/ + public static final int KEYCODE_MUHENKAN = 2608; + /** + * 换行键 + **/ + public static final int KEYCODE_LINEFEED = 2609; + /** + * 宏键 + **/ + public static final int KEYCODE_MACRO = 2610; + /** + * 数字键盘上的加号/减号键 + **/ + public static final int KEYCODE_NUMPAD_PLUSMINUS = 2611; + /** + * 扩展键 + **/ + public static final int KEYCODE_SCALE = 2612; + /** + * 日文韩语键 + **/ + public static final int KEYCODE_HANGUEL = 2613; + /** + * 日文汉语键 + **/ + public static final int KEYCODE_HANJA = 2614; + /** + * 日元键 + **/ + public static final int KEYCODE_YEN = 2615; + /** + * 停止键 + **/ + public static final int KEYCODE_STOP = 2616; + /** + * 重复键 + **/ + public static final int KEYCODE_AGAIN = 2617; + /** + * 道具键 + **/ + public static final int KEYCODE_PROPS = 2618; + /** + * 撤消键 + **/ + public static final int KEYCODE_UNDO = 2619; + /** + * 复制键 + **/ + public static final int KEYCODE_COPY = 2620; + /** + * 打开键 + **/ + public static final int KEYCODE_OPEN = 2621; + /** + * 粘贴键 + **/ + public static final int KEYCODE_PASTE = 2622; + /** + * 查找键 + **/ + public static final int KEYCODE_FIND = 2623; + /** + * 剪切键 + **/ + public static final int KEYCODE_CUT = 2624; + /** + * 帮助键 + **/ + public static final int KEYCODE_HELP = 2625; + /** + * 计算器特殊功能键,用于启动计算器应用程序 + **/ + public static final int KEYCODE_CALC = 2626; + /** + * 文件按键 + **/ + public static final int KEYCODE_FILE = 2627; + /** + * 书签键 + **/ + public static final int KEYCODE_BOOKMARKS = 2628; + /** + * 下一个按键 + **/ + public static final int KEYCODE_NEXT = 2629; + /** + * 播放/暂停键 + **/ + public static final int KEYCODE_PLAYPAUSE = 2630; + /** + * 上一个按键 + **/ + public static final int KEYCODE_PREVIOUS = 2631; + /** + * CD停止键 + **/ + public static final int KEYCODE_STOPCD = 2632; + /** + * 配置键 + **/ + public static final int KEYCODE_CONFIG = 2634; + /** + * 刷新键 + **/ + public static final int KEYCODE_REFRESH = 2635; + /** + * 退出键 + **/ + public static final int KEYCODE_EXIT = 2636; + /** + * 编辑键 + **/ + public static final int KEYCODE_EDIT = 2637; + /** + * 向上滚动键 + **/ + public static final int KEYCODE_SCROLLUP = 2638; + /** + * 向下滚动键 + **/ + public static final int KEYCODE_SCROLLDOWN = 2639; + /** + * 新建键 + **/ + public static final int KEYCODE_NEW = 2640; + /** + * 恢复键 + **/ + public static final int KEYCODE_REDO = 2641; + /** + * 关闭键 + **/ + public static final int KEYCODE_CLOSE = 2642; + /** + * 播放键 + **/ + public static final int KEYCODE_PLAY = 2643; + /** + * 低音增强键 + **/ + public static final int KEYCODE_BASSBOOST = 2644; + /** + * 打印键 + **/ + public static final int KEYCODE_PRINT = 2645; + /** + * 聊天键 + **/ + public static final int KEYCODE_CHAT = 2646; + /** + * 金融键 + **/ + public static final int KEYCODE_FINANCE = 2647; + /** + * 取消键 + **/ + public static final int KEYCODE_CANCEL = 2648; + /** + * 键盘灯光切换键 + **/ + public static final int KEYCODE_KBDILLUM_TOGGLE = 2649; + /** + * 键盘灯光调亮键 + **/ + public static final int KEYCODE_KBDILLUM_DOWN = 2650; + /** + * 键盘灯光调暗键 + **/ + public static final int KEYCODE_KBDILLUM_UP = 2651; + /** + * 发送键 + **/ + public static final int KEYCODE_SEND = 2652; + /** + * 答复键 + **/ + public static final int KEYCODE_REPLY = 2653; + /** + * 邮件转发键 + **/ + public static final int KEYCODE_FORWARDMAIL = 2654; + /** + * 保存键 + **/ + public static final int KEYCODE_SAVE = 2655; + /** + * 文件键 + **/ + public static final int KEYCODE_DOCUMENTS = 2656; + /** + * 下一个视频键 + **/ + public static final int KEYCODE_VIDEO_NEXT = 2657; + /** + * 上一个视频键 + **/ + public static final int KEYCODE_VIDEO_PREV = 2658; + /** + * 背光渐变键 + **/ + public static final int KEYCODE_BRIGHTNESS_CYCLE = 2659; + /** + * 亮度调节为0键 + **/ + public static final int KEYCODE_BRIGHTNESS_ZERO = 2660; + /** + * 显示关闭键 + **/ + public static final int KEYCODE_DISPLAY_OFF = 2661; + /** + * 游戏手柄上的各种按键 + **/ + public static final int KEYCODE_BTN_MISC = 2662; + /** + * 进入键 + **/ + public static final int KEYCODE_GOTO = 2663; + /** + * 信息查看键 + **/ + public static final int KEYCODE_INFO = 2664; + /** + * 程序键 + **/ + public static final int KEYCODE_PROGRAM = 2665; + /** + * 个人录像机(PVR)键 + **/ + public static final int KEYCODE_PVR = 2666; + /** + * 字幕键 + **/ + public static final int KEYCODE_SUBTITLE = 2667; + /** + * 全屏键 + **/ + public static final int KEYCODE_FULL_SCREEN = 2668; + /** + * 键盘 + **/ + public static final int KEYCODE_KEYBOARD = 2669; + /** + * 屏幕纵横比调节键 + **/ + public static final int KEYCODE_ASPECT_RATIO = 2670; + /** + * 端口控制键 + **/ + public static final int KEYCODE_PC = 2671; + /** + * TV键 + **/ + public static final int KEYCODE_TV = 2672; + /** + * TV键2 + **/ + public static final int KEYCODE_TV2 = 2673; + /** + * 录像机开启键 + **/ + public static final int KEYCODE_VCR = 2674; + /** + * 录像机开启键2 + **/ + public static final int KEYCODE_VCR2 = 2675; + /** + * SIM卡应用工具包(SAT)键 + **/ + public static final int KEYCODE_SAT = 2676; + /** + * CD键 + **/ + public static final int KEYCODE_CD = 2677; + /** + * 磁带键 + **/ + public static final int KEYCODE_TAPE = 2678; + /** + * 调谐器键 + **/ + public static final int KEYCODE_TUNER = 2679; + /** + * 播放器键 + **/ + public static final int KEYCODE_PLAYER = 2680; + /** + * DVD键 + **/ + public static final int KEYCODE_DVD = 2681; + /** + * 音频键 + **/ + public static final int KEYCODE_AUDIO = 2682; + /** + * 视频键 + **/ + public static final int KEYCODE_VIDEO = 2683; + /** + * 备忘录键 + **/ + public static final int KEYCODE_MEMO = 2684; + /** + * 日历键 + **/ + public static final int KEYCODE_CALENDAR = 2685; + /** + * 红色指示器 + **/ + public static final int KEYCODE_RED = 2686; + /** + * 绿色指示器 + **/ + public static final int KEYCODE_GREEN = 2687; + /** + * 黄色指示器 + **/ + public static final int KEYCODE_YELLOW = 2688; + /** + * 蓝色指示器 + **/ + public static final int KEYCODE_BLUE = 2689; + /** + * 频道向上键 + **/ + public static final int KEYCODE_CHANNELUP = 2690; + /** + * 频道向下键 + **/ + public static final int KEYCODE_CHANNELDOWN = 2691; + /** + * 末尾键 + **/ + public static final int KEYCODE_LAST = 2692; + /** + * 重启键 + **/ + public static final int KEYCODE_RESTART = 2693; + /** + * 慢速键 + **/ + public static final int KEYCODE_SLOW = 2694; + /** + * 随机播放键 + **/ + public static final int KEYCODE_SHUFFLE = 2695; + /** + * 可视电话键 + **/ + public static final int KEYCODE_VIDEOPHONE = 2696; + /** + * 游戏键 + **/ + public static final int KEYCODE_GAMES = 2697; + /** + * 放大键 + **/ + public static final int KEYCODE_ZOOMIN = 2698; + /** + * 缩小键 + **/ + public static final int KEYCODE_ZOOMOUT = 2699; + /** + * 缩放重置键 + **/ + public static final int KEYCODE_ZOOMRESET = 2700; + /** + * 文字处理键 + **/ + public static final int KEYCODE_WORDPROCESSOR = 2701; + /** + * 编辑器键 + **/ + public static final int KEYCODE_EDITOR = 2702; + /** + * 电子表格键 + **/ + public static final int KEYCODE_SPREADSHEET = 2703; + /** + * 图形编辑器键 + **/ + public static final int KEYCODE_GRAPHICSEDITOR = 2704; + /** + * 演示文稿键 + **/ + public static final int KEYCODE_PRESENTATION = 2705; + /** + * 数据库键标 + **/ + public static final int KEYCODE_DATABASE = 2706; + /** + * 新闻键 + **/ + public static final int KEYCODE_NEWS = 2707; + /** + * 语音信箱 + **/ + public static final int KEYCODE_VOICEMAIL = 2708; + /** + * 通讯簿 + **/ + public static final int KEYCODE_ADDRESSBOOK = 2709; + /** + * 通信键 + **/ + public static final int KEYCODE_MESSENGER = 2710; + /** + * 亮度切换键 + **/ + public static final int KEYCODE_BRIGHTNESS_TOGGLE = 2711; + /** + * AL拼写检查 + **/ + public static final int KEYCODE_SPELLCHECK = 2712; + /** + * 终端锁/屏幕保护程序 + **/ + public static final int KEYCODE_COFFEE = 2713; + /** + * 媒体循环键 + **/ + public static final int KEYCODE_MEDIA_REPEAT = 2714; + /** + * 图像键 + **/ + public static final int KEYCODE_IMAGES = 2715; + /** + * 按键配置键 + **/ + public static final int KEYCODE_BUTTONCONFIG = 2716; + /** + * 任务管理器 + **/ + public static final int KEYCODE_TASKMANAGER = 2717; + /** + * 日志按键 + **/ + public static final int KEYCODE_JOURNAL = 2718; + /** + * 控制面板键 + **/ + public static final int KEYCODE_CONTROLPANEL = 2719; + /** + * 应用程序选择键 + **/ + public static final int KEYCODE_APPSELECT = 2720; + /** + * 屏幕保护程序键 + **/ + public static final int KEYCODE_SCREENSAVER = 2721; + /** + * 辅助键 + **/ + public static final int KEYCODE_ASSISTANT = 2722; + /** + * 下一个键盘布局键 + **/ + public static final int KEYCODE_KBD_LAYOUT_NEXT = 2723; + /** + * 最小亮度键 + **/ + public static final int KEYCODE_BRIGHTNESS_MIN = 2724; + /** + * 最大亮度键 + **/ + public static final int KEYCODE_BRIGHTNESS_MAX = 2725; + /** + * 键盘输入Assist_Previous,查看输入法输入记录 + **/ + public static final int KEYCODE_KBDINPUTASSIST_PREV = 2726; + /** + * 键盘输入Assist_Next,查看输入法输入拓展 + **/ + public static final int KEYCODE_KBDINPUTASSIST_NEXT = 2727; + /** + * 键盘输入Assist_Previous,切换输入组中上一个输入法 + **/ + public static final int KEYCODE_KBDINPUTASSIST_PREVGROUP = 2728; + /** + * 键盘输入Assist_Next,切换输入组中下一个输入法 + **/ + public static final int KEYCODE_KBDINPUTASSIST_NEXTGROUP = 2729; + /** + * 键盘输入Assist_Accept + **/ + public static final int KEYCODE_KBDINPUTASSIST_ACCEPT = 2730; + /** + * 键盘输入Assist_Cancel + **/ + public static final int KEYCODE_KBDINPUTASSIST_CANCEL = 2731; + /** + * 挡风玻璃除雾器开关 + **/ + public static final int KEYCODE_FRONT = 2800; + /** + * 设置键 + **/ + public static final int KEYCODE_SETUP = 2801; + /** + * 唤醒键 + **/ + public static final int KEYCODE_WAKEUP = 2802; + /** + * 发送文件按键 + **/ + public static final int KEYCODE_SENDFILE = 2803; + /** + * 删除文件按键 + **/ + public static final int KEYCODE_DELETEFILE = 2804; + /** + * 文件传输(XFER)按键 + **/ + public static final int KEYCODE_XFER = 2805; + /** + * 程序键1 + **/ + public static final int KEYCODE_PROG1 = 2806; + /** + * 程序键2 + **/ + public static final int KEYCODE_PROG2 = 2807; + /** + * MS-DOS键(微软磁盘操作系统 + **/ + public static final int KEYCODE_MSDOS = 2808; + /** + * 屏幕锁定键 + **/ + public static final int KEYCODE_SCREENLOCK = 2809; + /** + * 方向旋转显示键 + **/ + public static final int KEYCODE_DIRECTION_ROTATE_DISPLAY = 2810; + /** + * Windows循环键 + **/ + public static final int KEYCODE_CYCLEWINDOWS = 2811; + /** + * 按键 + **/ + public static final int KEYCODE_COMPUTER = 2812; + /** + * 弹出CD键 + **/ + public static final int KEYCODE_EJECTCLOSECD = 2813; + /** + * ISO键 + **/ + public static final int KEYCODE_ISO = 2814; + /** + * 移动键 + **/ + public static final int KEYCODE_MOVE = 2815; + /** + * 按键'F13' + **/ + public static final int KEYCODE_F13 = 2816; + /** + * 按键'F14' + **/ + public static final int KEYCODE_F14 = 2817; + /** + * 按键'F15' + **/ + public static final int KEYCODE_F15 = 2818; + /** + * 按键'F16' + **/ + public static final int KEYCODE_F16 = 2819; + /** + * 按键'F17' + **/ + public static final int KEYCODE_F17 = 2820; + /** + * 按键'F18' + **/ + public static final int KEYCODE_F18 = 2821; + /** + * 按键'F19' + **/ + public static final int KEYCODE_F19 = 2822; + /** + * 按键'F20' + **/ + public static final int KEYCODE_F20 = 2823; + /** + * 按键'F21' + **/ + public static final int KEYCODE_F21 = 2824; + /** + * 按键'F22' + **/ + public static final int KEYCODE_F22 = 2825; + /** + * 按键'F23' + **/ + public static final int KEYCODE_F23 = 2826; + /** + * 按键'F24' + **/ + public static final int KEYCODE_F24 = 2827; + /** + * 程序键3 + **/ + public static final int KEYCODE_PROG3 = 2828; + /** + * 程序键4 + **/ + public static final int KEYCODE_PROG4 = 2829; + /** + * 仪表板 + **/ + public static final int KEYCODE_DASHBOARD = 2830; + /** + * 挂起键 + **/ + public static final int KEYCODE_SUSPEND = 2831; + /** + * 高阶路径键 + **/ + public static final int KEYCODE_HP = 2832; + /** + * 音量键 + **/ + public static final int KEYCODE_SOUND = 2833; + /** + * 疑问按键 + **/ + public static final int KEYCODE_QUESTION = 2834; + /** + * 连接键 + **/ + public static final int KEYCODE_CONNECT = 2836; + /** + * 运动按键 + **/ + public static final int KEYCODE_SPORT = 2837; + /** + * 商城键 + **/ + public static final int KEYCODE_SHOP = 2838; + /** + * 交替键 + **/ + public static final int KEYCODE_ALTERASE = 2839; + /** + * 在可用视频之间循环输出(监视器/LCD/TV输出/等) + **/ + public static final int KEYCODE_SWITCHVIDEOMODE = 2841; + /** + * 电池按键 + **/ + public static final int KEYCODE_BATTERY = 2842; + /** + * 蓝牙按键 + **/ + public static final int KEYCODE_BLUETOOTH = 2843; + /** + * 无线局域网 + **/ + public static final int KEYCODE_WLAN = 2844; + /** + * 超宽带(UWB) + **/ + public static final int KEYCODE_UWB = 2845; + /** + * WWAN WiMAX按键 + **/ + public static final int KEYCODE_WWAN_WIMAX = 2846; + /** + * 控制所有收音机的键 + **/ + public static final int KEYCODE_RFKILL = 2847; + /** + * 向上频道键 + **/ + public static final int KEYCODE_CHANNEL = 3001; + /** + * 按键0 + **/ + public static final int KEYCODE_BTN_0 = 3100; + /** + * 按键1 + **/ + public static final int KEYCODE_BTN_1 = 3101; + /** + * 按键2 + **/ + public static final int KEYCODE_BTN_2 = 3102; + /** + * 按键3 + **/ + public static final int KEYCODE_BTN_3 = 3103; + /** + * 按键4 + **/ + public static final int KEYCODE_BTN_4 = 3104; + /** + * 按键5 + **/ + public static final int KEYCODE_BTN_5 = 3105; + /** + * 按键6 + **/ + public static final int KEYCODE_BTN_6 = 3106; + /** + * 按键7 + **/ + public static final int KEYCODE_BTN_7 = 3107; + /** + * 按键8 + **/ + public static final int KEYCODE_BTN_8 = 3108; + /** + * 按键9 + **/ + public static final int KEYCODE_BTN_9 = 3109; + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/Hdc.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/Hdc.java new file mode 100644 index 0000000..39dc086 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hdc/Hdc.java @@ -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 listTargets() { + HDCSession session = createSession(); + if (session == null) { + return new ArrayList<>(); + } + return listTargets(session); + } + + /** + * 通过指定session获得当前设备列表 + * + * @param session HDC会话 + * @return 设备列表,如果发生错误,则为null + */ + public ArrayList 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 result = new ArrayList(); + 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 listForward() { + ArrayList rules = new ArrayList(); + 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; + } + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/AgentABI.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/AgentABI.java new file mode 100644 index 0000000..1b915e1 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/AgentABI.java @@ -0,0 +1,7 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium; + +public enum AgentABI { + ARM64, + X86, + UNKNOWN +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/HyppiumAgent.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/HyppiumAgent.java new file mode 100644 index 0000000..c039867 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/HyppiumAgent.java @@ -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控制和获取设备信息 + *
+ * 鸿蒙后门:通过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的端口 + *
+ * 此端口转发至设备内部的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 + *
+ * 会杀死之前的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; + } + + /** + * 需要的端口转发是否存在 + *
+ * 若存在,则使用已存在的记录 + * + * @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; + } + + /** + * 创建需要的端口转发 + *
+ * 会重新寻找一个可用端口 + * + * @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 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, "按下主屏键"); + } + + /** + * 按下最近应用按钮 + *
+ * 目前只是滑动屏幕中心,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); + } + + /** + * 获得屏幕方向 + *
+ * + * @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 displayRotationData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken>() { + }.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 resultData = HarmonyDeviceManager.gson.fromJson(jsonText, new TypeToken>() { + }.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; + } + } + + /** + * 捕获到数据的回调 + *
+ * 用于图片流或者UI层级树的持续获取 + */ + public interface OnCaptureData { + /** + * 当捕获的目标数据到来时 + *
+ * 此方法将在Session线程运行 + * + * @param data 数据 + */ + void onCaptureData(byte[] data); + } + + /** + * 要开始的指定数据捕捉已经正在运行 + *
+ * 需要进行一次取消后再次开始 + */ + public static class CaptureAlreadyRunningException extends Exception { + public CaptureAlreadyRunningException(String message) { + super(message + "已经正在运行"); + } + } + +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestCodeProvider.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestCodeProvider.java new file mode 100644 index 0000000..d4209c7 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestCodeProvider.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestData.java new file mode 100644 index 0000000..a15a5fb --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/RequestData.java @@ -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 { + private final Logger log = LoggerFactory.getLogger(RequestData.class); + private final String module = "com.ohos.devicetest.hypiumApiHelper"; + private final String method; + private final Params 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 { + String api; + T args = null; + + private Params() { + + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/Response.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/Response.java new file mode 100644 index 0000000..20eca69 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/Response.java @@ -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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/StaticRequestData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/StaticRequestData.java new file mode 100644 index 0000000..5bbea3d --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/StaticRequestData.java @@ -0,0 +1,18 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium; + +public abstract class StaticRequestData extends RequestData { + + 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; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/AbstractTouch.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/AbstractTouch.java new file mode 100644 index 0000000..c63c3fc --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/AbstractTouch.java @@ -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 { + + public AbstractTouch(String api, int x, int y) { + super("Gestures", api, new PositionData(x, y)); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/CaptureLayout.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/CaptureLayout.java new file mode 100644 index 0000000..3731805 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/CaptureLayout.java @@ -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 { + private static CaptureLayout instance; + + public static CaptureLayout getInstance() { + if (instance == null) { + instance = new CaptureLayout(); + } + return instance; + } + + private CaptureLayout() { + super("Captures", "captureLayout", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplayRotation.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplayRotation.java new file mode 100644 index 0000000..b22d82d --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplayRotation.java @@ -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 { + + private static GetDisplayRotation instance; + + public static GetDisplayRotation getInstance() { + if (instance == null) { + instance = new GetDisplayRotation(); + } + return instance; + } + + private GetDisplayRotation() { + super("CtrlCmd", "getDisplayRotation", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplaySize.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplaySize.java new file mode 100644 index 0000000..98d99f8 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/GetDisplaySize.java @@ -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 { + + private static GetDisplaySize instance; + + public static GetDisplaySize getInstance() { + if (instance == null) { + instance = new GetDisplaySize(); + } + return instance; + } + + private GetDisplaySize() { + super("CtrlCmd", "getDisplaySize", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/Pinch.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/Pinch.java new file mode 100644 index 0000000..fbd1409 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/Pinch.java @@ -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 { + public static Pinch getInstance(Float scale) { + return new Pinch(scale); + } + + private Pinch(Float scale) { + super("Gestures", "pinch", new ScaleData(scale)); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressBack.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressBack.java new file mode 100644 index 0000000..a859dfc --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressBack.java @@ -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 { + + private static PressBack instance; + + public static PressBack getInstance() { + if (instance == null) { + instance = new PressBack(); + } + return instance; + } + + private PressBack() { + super("Gestures", "pressBack", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressDownVolume.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressDownVolume.java new file mode 100644 index 0000000..af691f7 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressDownVolume.java @@ -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 { + private static PressDownVolume instance; + + public static PressDownVolume getInstance() { + if (instance == null) { + instance = new PressDownVolume(); + } + return instance; + } + + private PressDownVolume() { + super("CtrlCmd", "downVolume", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressHome.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressHome.java new file mode 100644 index 0000000..7e8b711 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressHome.java @@ -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 { + + private static PressHome instance; + + public static PressHome getInstance() { + if (instance == null) { + instance = new PressHome(); + } + return instance; + } + + private PressHome() { + super("Gestures", "pressHome", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressPowerKey.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressPowerKey.java new file mode 100644 index 0000000..5ad6c28 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressPowerKey.java @@ -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 { + private static PressPowerKey instance; + + public static PressPowerKey getInstance() { + if (instance == null) { + instance = new PressPowerKey(); + } + return instance; + } + + private PressPowerKey() { + super("CtrlCmd", "pressPowerKey", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressRecentApp.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressRecentApp.java new file mode 100644 index 0000000..aeee768 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressRecentApp.java @@ -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 { + + private static PressRecentApp instance; + + public static PressRecentApp getInstance() { + if (instance == null) { + instance = new PressRecentApp(); + } + return instance; + } + + private PressRecentApp() { + super("Gestures", "pressRecentApp", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressUpVolume.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressUpVolume.java new file mode 100644 index 0000000..119d726 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/PressUpVolume.java @@ -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 { + private static PressUpVolume instance; + + public static PressUpVolume getInstance() { + if (instance == null) { + instance = new PressUpVolume(); + } + return instance; + } + + private PressUpVolume() { + super("CtrlCmd", "upVolume", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/RotationDisplay.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/RotationDisplay.java new file mode 100644 index 0000000..543fd03 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/RotationDisplay.java @@ -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.DisplayRotationData; + +public class RotationDisplay extends RequestData { + + public RotationDisplay(int direction) { + super("CtrlCmd", "rotationDisplay", new DisplayRotationData(direction)); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureScreenStream.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureScreenStream.java new file mode 100644 index 0000000..83fd4d4 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureScreenStream.java @@ -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.CaptureScreenData; + +/** + * 动作:开始捕捉屏幕图片流数据 + */ +public class StartCaptureScreenStream extends RequestData { + public static StartCaptureScreenStream getInstance(float scale) { + return new StartCaptureScreenStream(scale); + } + + private StartCaptureScreenStream(float scale) { + super("Captures", "startCaptureScreen", new CaptureScreenData(scale)); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureUiAction.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureUiAction.java new file mode 100644 index 0000000..3fd77d3 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StartCaptureUiAction.java @@ -0,0 +1,22 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + + +import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData; + +/** + * 动作:开始捕捉UI动作,会返回UI层级树 + */ +public class StartCaptureUiAction extends StaticRequestData { + private static StartCaptureUiAction instance; + + public static StartCaptureUiAction getInstance() { + if (instance == null) { + instance = new StartCaptureUiAction(); + } + return instance; + } + + private StartCaptureUiAction() { + super("Captures", "startCaptureUiAction", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureScreenStream.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureScreenStream.java new file mode 100644 index 0000000..2a1db81 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureScreenStream.java @@ -0,0 +1,19 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + + +import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData; + +public class StopCaptureScreenStream extends StaticRequestData { + private static StopCaptureScreenStream instance; + + public static StopCaptureScreenStream getInstance() { + if (instance == null) { + instance = new StopCaptureScreenStream(); + } + return instance; + } + + private StopCaptureScreenStream() { + super("Captures", "stopCaptureScreen", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureUiAction.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureUiAction.java new file mode 100644 index 0000000..b14f9c7 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/StopCaptureUiAction.java @@ -0,0 +1,22 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + + +import net.northking.cctp.upperComputer.driver.harmony.hyppium.StaticRequestData; + +/** + * 动作:停止捕捉UI动作 + */ +public class StopCaptureUiAction extends StaticRequestData { + private static StopCaptureUiAction instance; + + public static StopCaptureUiAction getInstance() { + if (instance == null) { + instance = new StopCaptureUiAction(); + } + return instance; + } + + private StopCaptureUiAction() { + super("Captures", "stopCaptureUiAction", new Object()); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchDown.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchDown.java new file mode 100644 index 0000000..3b21815 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchDown.java @@ -0,0 +1,10 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + +/** + * 动作:单指在指定坐标触摸按下 + */ +public class TouchDown extends AbstractTouch { + public TouchDown(int x, int y) { + super("touchDown", x, y); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchMove.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchMove.java new file mode 100644 index 0000000..cebac0f --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchMove.java @@ -0,0 +1,10 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + +/** + * 动作:单指触摸移动到指定坐标 + */ +public class TouchMove extends AbstractTouch { + public TouchMove(int x, int y) { + super("touchMove", x, y); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchUp.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchUp.java new file mode 100644 index 0000000..4945e38 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/action/TouchUp.java @@ -0,0 +1,10 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.action; + +/** + * 动作:单指触摸移动到指定坐标 + */ +public class TouchUp extends AbstractTouch { + public TouchUp(int x, int y) { + super("touchUp", x, y); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureLayoutData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureLayoutData.java new file mode 100644 index 0000000..f2cf340 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureLayoutData.java @@ -0,0 +1,8 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + + +import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent; + +public class CaptureLayoutData { + public UiComponent result; +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureScreenData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureScreenData.java new file mode 100644 index 0000000..a82f466 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/CaptureScreenData.java @@ -0,0 +1,9 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public class CaptureScreenData { + private final ScaleData options = new ScaleData(1f); + + public CaptureScreenData(float scale) { + options.setScale(scale); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplayRotationData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplayRotationData.java new file mode 100644 index 0000000..6996338 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplayRotationData.java @@ -0,0 +1,11 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public class DisplayRotationData { + public int direction = 0; + + public DisplayRotationData(int direction) { + if (direction > 3 || direction < 0) + throw new IllegalArgumentException("屏幕方向的值应该为[0,3],实参:" + direction); + this.direction = direction; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplaySize.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplaySize.java new file mode 100644 index 0000000..c86c1d2 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/DisplaySize.java @@ -0,0 +1,9 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +/** + * 屏幕大小数据 + */ +public class DisplaySize { + public int width; + public int height; +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/PositionData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/PositionData.java new file mode 100644 index 0000000..1817615 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/PositionData.java @@ -0,0 +1,11 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public class PositionData { + int x; + int y; + + public PositionData(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ResultData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ResultData.java new file mode 100644 index 0000000..9530a97 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ResultData.java @@ -0,0 +1,5 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public class ResultData { + public T result; +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ScaleData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ScaleData.java new file mode 100644 index 0000000..fdabea4 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/ScaleData.java @@ -0,0 +1,16 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public class ScaleData { + private Float scale = null; + + public ScaleData(float scale) { + setScale(scale); + } + + public void setScale(Float scale) { + this.scale = scale; + if (scale == 1.0f) { + this.scale = null; + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventData.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventData.java new file mode 100644 index 0000000..48e62bb --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventData.java @@ -0,0 +1,81 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +import com.google.gson.annotations.SerializedName; +import net.northking.cctp.upperComputer.driver.protocol.packet.CodecUtils; +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; + +public class UiEventData implements ICommandData { + @SerializedName("ABILITY") + private String abilityName; + + @SerializedName("BUNDLE") + private String bundleName; + + @SerializedName("CENTER_X") + private double centerX; + + @SerializedName("CENTER_Y") + private double centerY; + + @SerializedName("EVENT_TYPE") + private String eventType; + + @SerializedName("LENGTH") + private int length; + + @SerializedName("OP_TYPE") + private String operationType; + + @SerializedName("VELO") + private double velocity; + + @SerializedName("direction.X") + private double directionX; + + @SerializedName("direction.Y") + private double directionY; + + private long duration; + + private int fingerNumber; + + private ArrayList fingerList = new ArrayList<>(0); + + public UiEventData() { + + } + + public UiEventData(DataInput dataInput) throws IOException { + buildFromInput(dataInput); + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + abilityName = CodecUtils.readNullable(dataInput, CodecUtils::readText); + bundleName = CodecUtils.readNullable(dataInput, CodecUtils::readText); + centerX = dataInput.readDouble(); + centerY = dataInput.readDouble(); + eventType = CodecUtils.readText(dataInput); + length = dataInput.readInt(); + operationType = CodecUtils.readText(dataInput); + velocity = dataInput.readDouble(); + directionX = dataInput.readDouble(); + directionY = dataInput.readDouble(); + duration = dataInput.readLong(); + fingerNumber = dataInput.readInt(); + int listCount = dataInput.readInt(); + for (int i = 0; i < listCount; i++) { + UiEventFinger finger = new UiEventFinger(); + } + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + + } +} \ No newline at end of file diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventFinger.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventFinger.java new file mode 100644 index 0000000..d0a434d --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventFinger.java @@ -0,0 +1,111 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +import com.google.gson.annotations.SerializedName; +import net.northking.cctp.upperComputer.driver.protocol.packet.CodecUtils; +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class UiEventFinger implements ICommandData { + @SerializedName("X_POSI") + private int firstTouchX; + @SerializedName("Y_POSI") + private int firstTouchY; + @SerializedName("W1_ID") + private String firstEventId; + @SerializedName("W1_Type") + private String firstEventType; + @SerializedName("W1_Text") + private String firstEventText; + @SerializedName("W1_BOUNDS") + private String firstEventBounds; + @SerializedName("W1_HIER") + private String firstEventHierarchy; + @SerializedName("X2_POSI") + private int lastTouchX; + @SerializedName("Y2_POSI") + private int lastTouchY; + @SerializedName("W2_ID") + private String lastEventId; + @SerializedName("W2_Type") + private String lastEventType; + @SerializedName("W2_Text") + private String lastEventText; + @SerializedName("W2_BOUNDS") + private String lastEventBounds; + @SerializedName("W2_HIER") + private String lastEventHierarchy; + @SerializedName("LENGTH") + private int stepLength; + /** + * 根据源码:应该最大值为固定40000 + */ + @SerializedName("MAX_VEL") + private int maxVelocity; + @SerializedName("VELO") + private double velocity; + @SerializedName("direction.X") + private double directionX; + @SerializedName("direction.Y") + private double directionY; + + public UiEventFinger() { + + } + + public UiEventFinger(DataInput dataInput) throws IOException { + buildFromInput(dataInput); + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + firstTouchX = dataInput.readInt(); + firstTouchY = dataInput.readInt(); + firstEventId = CodecUtils.readNullable(dataInput, CodecUtils::readText); + firstEventType = CodecUtils.readNullable(dataInput, CodecUtils::readText); + firstEventText = CodecUtils.readNullable(dataInput, CodecUtils::readText); + firstEventBounds = CodecUtils.readNullable(dataInput, CodecUtils::readText); + firstEventHierarchy = CodecUtils.readNullable(dataInput, CodecUtils::readText); + + lastTouchX = dataInput.readInt(); + lastTouchY = dataInput.readInt(); + lastEventId = CodecUtils.readNullable(dataInput, CodecUtils::readText); + lastEventType = CodecUtils.readNullable(dataInput, CodecUtils::readText); + lastEventText = CodecUtils.readNullable(dataInput, CodecUtils::readText); + lastEventBounds = CodecUtils.readNullable(dataInput, CodecUtils::readText); + lastEventHierarchy = CodecUtils.readNullable(dataInput, CodecUtils::readText); + + stepLength = dataInput.readInt(); + maxVelocity = dataInput.readInt(); + velocity = dataInput.readDouble(); + directionX = dataInput.readDouble(); + directionY = dataInput.readDouble(); + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + dataOutput.writeInt(firstTouchX); + dataOutput.writeInt(firstTouchY); + CodecUtils.writeNullable(dataOutput, firstEventId, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, firstEventType, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, firstEventText, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, firstEventBounds, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, firstEventHierarchy, CodecUtils::writeText); + + dataOutput.writeInt(lastTouchX); + dataOutput.writeInt(lastTouchY); + CodecUtils.writeNullable(dataOutput, lastEventId, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, lastEventType, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, lastEventText, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, lastEventBounds, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, lastEventHierarchy, CodecUtils::writeText); + + dataOutput.writeInt(stepLength); + dataOutput.writeInt(maxVelocity); + dataOutput.writeDouble(velocity); + dataOutput.writeDouble(directionX); + dataOutput.writeDouble(directionY); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventResponse.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventResponse.java new file mode 100644 index 0000000..f77941b --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventResponse.java @@ -0,0 +1,55 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + + +import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent; +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class UiEventResponse implements ICommandData { + private int code; + private UiEventData data = null; + private UiComponent layout = null; + + public UiEventStage getStage() { + return UiEventStage.fromValue(code); + } + + public UiEventData getData() { + return data; + } + + public UiComponent getLayout() { + return layout; + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + code = dataInput.readInt(); + if (dataInput.readBoolean()) { + data = new UiEventData(dataInput); + } else { + data = null; + } + if (dataInput.readBoolean()) { + layout = new UiComponent(dataInput); + } else { + layout = null; + } + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + dataOutput.writeInt(code); + dataOutput.writeBoolean(data != null); + if (data != null) { + data.encodeToOutput(dataOutput); + } + dataOutput.writeBoolean(layout != null); + if (layout != null) { + layout.encodeToOutput(dataOutput); + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventStage.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventStage.java new file mode 100644 index 0000000..2898531 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/hyppium/data/UiEventStage.java @@ -0,0 +1,25 @@ +package net.northking.cctp.upperComputer.driver.harmony.hyppium.data; + +public enum UiEventStage { + StartUp(0), + StartEnd(1), + StartFindWidgets(2), + StartFindWidgetsEnd(3); + + private final int value; + + UiEventStage(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static UiEventStage fromValue(int value) { + for (UiEventStage stage : values()) { + if (stage.value == value) return stage; + } + throw new IllegalArgumentException("Invalid value for UiEventStage: " + value); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiAttribute.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiAttribute.java new file mode 100644 index 0000000..ffcfc5b --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiAttribute.java @@ -0,0 +1,293 @@ +package net.northking.cctp.upperComputer.driver.harmony.ui; + + +import net.northking.cctp.upperComputer.driver.protocol.packet.CodecUtils; +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * UI元素属性 + */ +public class UiAttribute implements ICommandData { + private String abilityName; + private long accessibilityId; + private String backgroundColor; + private String backgroundImage; + private String blur; + /** + * 大小 + * 非空 + */ + private String bounds; + private UiRect boundRect = null; + private String bundleName; + private boolean checkable = false; + private String description; + private boolean enabled = false; + private boolean focused = false; + private String hashcode; + private String hierarchy; + private String hint; + private String hitTestBehavior; + private int hostWindowId; + private String id; + private String key; + private boolean longClickable = false; + private double opacity; + private String origBounds; + private UiRect originalBoundRect = null; + private String pagePath; + private boolean scrollable = false; + private boolean selected = false; + private String text; + private String type; + private boolean visible = false; + private int zIndex; + + public UiAttribute() { + + } + + public UiAttribute(DataInput dataInput) throws IOException { + buildFromInput(dataInput); + } + + private boolean parseBooleanValue(String value) { + return "true".equals(value); + } + + public String getAbilityName() { + return abilityName; + } + + public long getAccessibilityId() { + return accessibilityId; + } + + public String getBackgroundColor() { + return backgroundColor; + } + + public String getBackgroundImage() { + return backgroundImage; + } + + /** + * 模糊像素 + *
+ * 例如0.00px + * + * @return + */ + public String getBlur() { + return blur; + } + + public UiRect getBoundRect() { + if (boundRect == null) { + boundRect = new UiRect(bounds); + } + return boundRect; + } + + public String getBundleName() { + return bundleName; + } + + public boolean isCheckable() { + return checkable; + } + + public String getDescription() { + return description; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isFocused() { + return focused; + } + + public String getHash() { + return hashcode; + } + + /** + * 层级信息 + *
+ * 例如:ROOT34,0,2,0,0,0,0,0,0,0,0 + * + * @return + */ + public String getHierarchy() { + return hierarchy; + } + + public String getHint() { + return hint; + } + + public String getHitTestBehavior() { + return hitTestBehavior; + } + + public int getHostWindowId() { + return hostWindowId; + } + + public String getId() { + return id; + } + + public String getKey() { + return key; + } + + public boolean isLongClickable() { + return longClickable; + } + + public double getOpacity() { + return opacity; + } + + public UiRect getOriginalBoundRect() { + if (originalBoundRect == null) { + originalBoundRect = new UiRect(origBounds); + } + return originalBoundRect; + } + + public String getPagePath() { + return pagePath; + } + + public boolean isScrollable() { + return scrollable; + } + + public boolean isSelected() { + return selected; + } + + public String getText() { + return text; + } + + public String getType() { + return type; + } + + public boolean isVisible() { + return visible; + } + + public int getZIndex() { + return zIndex; + } + + @Override + public String toString() { + return "UiAttribute{" + + "abilityName='" + abilityName + '\'' + + ", accessibilityId='" + accessibilityId + '\'' + + ", backgroundColor='" + backgroundColor + '\'' + + ", backgroundImage='" + backgroundImage + '\'' + + ", blur='" + blur + '\'' + + ", bounds='" + bounds + '\'' + + ", bundleName='" + bundleName + '\'' + + ", checkable='" + checkable + '\'' + + ", description='" + description + '\'' + + ", enabled='" + enabled + '\'' + + ", focused='" + focused + '\'' + + ", hashcode='" + hashcode + '\'' + + ", hierarchy='" + hierarchy + '\'' + + ", hint='" + hint + '\'' + + ", hitTestBehavior='" + hitTestBehavior + '\'' + + ", hostWindowId='" + hostWindowId + '\'' + + ", id='" + id + '\'' + + ", key='" + key + '\'' + + ", longClickable='" + longClickable + '\'' + + ", opacity='" + opacity + '\'' + + ", origBounds='" + origBounds + '\'' + + ", pagePath='" + pagePath + '\'' + + ", scrollable='" + scrollable + '\'' + + ", selected='" + selected + '\'' + + ", text='" + text + '\'' + + ", type='" + type + '\'' + + ", visible='" + visible + '\'' + + ", zIndex='" + zIndex + '\'' + + '}'; + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + abilityName = CodecUtils.readNullable(dataInput, CodecUtils::readText); + accessibilityId = dataInput.readLong(); + backgroundColor = CodecUtils.readNullable(dataInput, CodecUtils::readText); + backgroundImage = CodecUtils.readNullable(dataInput, CodecUtils::readText); + blur = CodecUtils.readNullable(dataInput, CodecUtils::readText); + boundRect = new UiRect(dataInput); + bounds = boundRect.toString(); + bundleName = CodecUtils.readNullable(dataInput, CodecUtils::readText); + checkable = dataInput.readBoolean(); + description = CodecUtils.readNullable(dataInput, CodecUtils::readText); + enabled = dataInput.readBoolean(); + focused = dataInput.readBoolean(); + hashcode = CodecUtils.readNullable(dataInput, CodecUtils::readText); + hierarchy = CodecUtils.readNullable(dataInput, CodecUtils::readText); + hint = CodecUtils.readNullable(dataInput, CodecUtils::readText); + hitTestBehavior = CodecUtils.readNullable(dataInput, CodecUtils::readText); + hostWindowId = dataInput.readInt(); + id = CodecUtils.readNullable(dataInput, CodecUtils::readText); + key = CodecUtils.readNullable(dataInput, CodecUtils::readText); + longClickable = dataInput.readBoolean(); + opacity = dataInput.readDouble(); + originalBoundRect = new UiRect(dataInput); + origBounds = originalBoundRect.toString(); + pagePath = CodecUtils.readNullable(dataInput, CodecUtils::readText); + scrollable = dataInput.readBoolean(); + selected = dataInput.readBoolean(); + text = CodecUtils.readNullable(dataInput, CodecUtils::readText); + type = CodecUtils.readNullable(dataInput, CodecUtils::readText); + visible = dataInput.readBoolean(); + zIndex = dataInput.readInt(); + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + CodecUtils.writeNullable(dataOutput, abilityName, CodecUtils::writeText); + dataOutput.writeLong(accessibilityId); + CodecUtils.writeNullable(dataOutput, backgroundColor, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, backgroundImage, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, blur, CodecUtils::writeText); + getBoundRect().encodeToOutput(dataOutput); + CodecUtils.writeNullable(dataOutput, bundleName, CodecUtils::writeText); + dataOutput.writeBoolean(checkable); + CodecUtils.writeNullable(dataOutput, description, CodecUtils::writeText); + dataOutput.writeBoolean(enabled); + dataOutput.writeBoolean(focused); + CodecUtils.writeNullable(dataOutput, hashcode, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, hierarchy, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, hint, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, hitTestBehavior, CodecUtils::writeText); + dataOutput.writeInt(hostWindowId); + CodecUtils.writeNullable(dataOutput, id, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, key, CodecUtils::writeText); + dataOutput.writeBoolean(longClickable); + dataOutput.writeDouble(opacity); + getOriginalBoundRect().encodeToOutput(dataOutput); + CodecUtils.writeNullable(dataOutput, pagePath, CodecUtils::writeText); + dataOutput.writeBoolean(scrollable); + dataOutput.writeBoolean(selected); + CodecUtils.writeNullable(dataOutput, text, CodecUtils::writeText); + CodecUtils.writeNullable(dataOutput, type, CodecUtils::writeText); + dataOutput.writeBoolean(visible); + dataOutput.writeInt(zIndex); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiComponent.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiComponent.java new file mode 100644 index 0000000..59cfaf6 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiComponent.java @@ -0,0 +1,78 @@ +package net.northking.cctp.upperComputer.driver.harmony.ui; + + +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; + +/** + * UI元素节点 + */ +public class UiComponent implements ICommandData, XMLEntity { + public UiAttribute attributes; + public ArrayList children = new ArrayList<>(); + + public UiComponent() { + + } + + public UiComponent(DataInput dataInput) throws IOException { + buildFromInput(dataInput); + } + + @Override + public String toString() { + return "UiComponent{" + + "attributes=" + attributes + + ", children=" + children + + '}'; + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + attributes = new UiAttribute(dataInput); + int childCount = dataInput.readInt(); + for (int i = 0; i < childCount; i++) { + children.add(new UiComponent(dataInput)); + } + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + attributes.encodeToOutput(dataOutput); + dataOutput.writeInt(children.size()); + for (UiComponent child : children) { + child.encodeToOutput(dataOutput); + } + } + + @Override + public Node createXMLElement(Document document) { + String type = attributes.getType(); + if (type == null) { + type = "Root"; + } + String text = attributes.getText(); + if (text == null) { + text = ""; + } + Element element = document.createElement(type); + element.setAttribute("id", attributes.getId()); + element.setAttribute("x", String.valueOf(attributes.getBoundRect().getLeft())); + element.setAttribute("y", String.valueOf(attributes.getBoundRect().getTop())); + element.setAttribute("width", String.valueOf(attributes.getBoundRect().getWidth())); + element.setAttribute("height", String.valueOf(attributes.getBoundRect().getHeight())); + element.setAttribute("text", text); + for (UiComponent child : children) { + Node childElement = child.createXMLElement(document); + element.appendChild(childElement); + } + return element; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiRect.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiRect.java new file mode 100644 index 0000000..75635d6 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/UiRect.java @@ -0,0 +1,83 @@ +package net.northking.cctp.upperComputer.driver.harmony.ui; + + +import net.northking.cctp.upperComputer.driver.protocol.packet.ICommandData; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class UiRect implements ICommandData { + private int left, top, right, bottom; + + /** + * Rect文本,例如 [0,0][1216,2688] + * + * @param text + */ + public UiRect(String text) { + if (text == null) return; + if (text.length() < 10) throw new IllegalArgumentException("无法解析的UiRect字符串: " + text); + try { + left = Integer.parseInt(text.substring(1, text.indexOf(","))); + top = Integer.parseInt(text.substring(text.indexOf(",") + 1, text.indexOf("]["))); + right = Integer.parseInt(text.substring(text.indexOf("][") + 2, text.lastIndexOf(","))); + bottom = Integer.parseInt(text.substring(text.lastIndexOf(",") + 1, text.length() - 1)); + } catch (Exception e) { + throw new IllegalArgumentException("无法解析UiRect字符串", e); + } + } + + public UiRect(DataInput dataInput) throws IOException { + this.buildFromInput(dataInput); + } + + public int getLeft() { + return left; + } + + public int getTop() { + return top; + } + + public int getRight() { + return right; + } + + public int getBottom() { + return bottom; + } + + public int getWidth() { + return right - left; + } + + public int getHeight() { + return bottom - top; + } + + @Override + public String toString() { + return "[" + left + + "," + top + + "][" + right + + "," + bottom + + ']'; + } + + @Override + public void buildFromInput(DataInput dataInput) throws IOException { + left = dataInput.readInt(); + top = dataInput.readInt(); + right = dataInput.readInt(); + bottom = dataInput.readInt(); + } + + @Override + public void encodeToOutput(DataOutput dataOutput) throws IOException { + dataOutput.writeInt(left); + dataOutput.writeInt(top); + dataOutput.writeInt(right); + dataOutput.writeInt(bottom); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/XMLEntity.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/XMLEntity.java new file mode 100644 index 0000000..bbad243 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/driver/harmony/ui/XMLEntity.java @@ -0,0 +1,18 @@ +package net.northking.cctp.upperComputer.driver.harmony.ui; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * 这是一个可以处理为XML格式文本的数据 + */ +public interface XMLEntity { + + /** + * 创建XML节点 + * + * @param document 文档对象,用于创建XML节点 + * @return 返回一个XML节点对象,该节点代表了当前实体的结构和数据。 + */ + Node createXMLElement(Document document); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/init/UpperComputerInit.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/init/UpperComputerInit.java index ab181b5..b39f5ee 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/init/UpperComputerInit.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/init/UpperComputerInit.java @@ -2,6 +2,7 @@ package net.northking.cctp.upperComputer.init; 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.UpperComputerManager; import org.slf4j.Logger; @@ -51,6 +52,10 @@ public class UpperComputerInit implements ApplicationRunner, ApplicationListener //5.开启android设备监听 logger.info("6.开始启动android设备监控。。。。。。"); AndroidDeviceManager.getInstance().start(); + + //6.开启Harmony设备的监听 + logger.info("7.开始启动Harmony设备监控。。。。。。"); + HarmonyDeviceManager.getInstance(); } } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/service/impl/ConnectionServiceImpl.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/service/impl/ConnectionServiceImpl.java index 094ea4b..8d4bb76 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/service/impl/ConnectionServiceImpl.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/service/impl/ConnectionServiceImpl.java @@ -1,18 +1,18 @@ package net.northking.cctp.upperComputer.service.impl; 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.UpperComputerManager; 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.exception.ExecuteException; import net.northking.cctp.upperComputer.exception.ParamMistakeException; import net.northking.cctp.upperComputer.service.DeviceConnectionService; import net.northking.cctp.upperComputer.webSocket.entity.CmdRequest; -import net.northking.cctp.upperComputer.webSocket.thread.AndroidMessageHandlerThread; -import net.northking.cctp.upperComputer.webSocket.thread.IosMacMessageHandlerThread; -import net.northking.cctp.upperComputer.webSocket.thread.IosWindowsAndLinuxMessageHandlerThread; -import net.northking.cctp.upperComputer.webSocket.thread.MessageHandler; +import net.northking.cctp.upperComputer.webSocket.thread.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -100,6 +100,37 @@ public class ConnectionServiceImpl implements DeviceConnectionService { } logger.info("手机[{}]初始化环境完成。。。。。。。", serial); return true; + }else if ("harmony".equals(type)) { + logger.info("手机[{}]平台为harmony", type); + boolean isOnline = HarmonyDeviceManager.getInstance().checkMobileIsOnline(serial); + if (!isOnline) { + logger.warn("当前设备【{}】已经离线",serial); + return false; + } + HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(serial); + if (null == currentDevice || currentDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED) { + logger.warn("当前设备【{}】已经离线",serial); + return false; + } + //消息处理线程 + if (null == handleMessage) { + logger.info("harmony设备【{}】的消息处理线程不存在,新创建一个", serial); + try { + handleMessage = new HarmonyMessageHandlerThread(currentDevice, session); + handleMessage.startHandMessage(); + messageHandleMap.put(serial, handleMessage); + } catch (ParamMistakeException e) { + logger.error("手机[{}]初始化环境出错", serial, e); + return false; + } catch (ExecuteException e) { + logger.error("手机[{}]初始化环境出错", serial, e); + return false; + } + } else { + logger.info("android设备【{}】的消息处理线程已存在,无需创建", serial); + } + logger.info("手机[{}]初始化环境完成。。。。。。。", serial); + return true; } else { logger.info("手机[{}]平台不正确,选择的平台为:{}。。。。。。。", serial,type); return false; diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/IosDeviceHandleHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/AbstractDeviceHelper.java similarity index 58% rename from cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/IosDeviceHandleHelper.java rename to cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/AbstractDeviceHelper.java index add4bb4..8f51949 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/IosDeviceHandleHelper.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/AbstractDeviceHelper.java @@ -1,8 +1,6 @@ -package net.northking.cctp.upperComputer.utils.ios; +package net.northking.cctp.upperComputer.utils.deviceHepler; import cn.hutool.core.img.ImgUtil; -import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; -import net.northking.cctp.upperComputer.entity.PhoneEntity; import net.northking.cctp.upperComputer.exception.ExecuteException; import net.northking.cctp.upperComputer.utils.HttpUtils; import net.northking.cctp.upperComputer.utils.SpringUtils; @@ -10,27 +8,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; import java.util.UUID; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; /** * @author : yineng.huang - * @date : 2024/4/24 16:44 + * @date : 2024/10/29 17:47 */ -public abstract class IosDeviceHandleHelper { +public abstract class AbstractDeviceHelper implements DeviceHelper{ - private final Logger logger = LoggerFactory.getLogger(IosDeviceHandleHelper.class); - - protected final String LAUNCH_APP_FAIL_FLAG = "invalid code signature"; //应用未信任启动失败标志 - - public abstract boolean isAppInstalled(String deviceId, String appPackage); - - public abstract boolean removeApp(String deviceId, String appPackage); - - public abstract String getOldPackageCode(String deviceId, String appPackage); + private final Logger logger = LoggerFactory.getLogger(AbstractDeviceHelper.class); public String downloadAppToLocal(String appId, String appName) { File fileDir = new File(System.getProperty("user.dir") + "/tempApp/"); @@ -38,37 +24,34 @@ public abstract class IosDeviceHandleHelper { fileDir.mkdirs(); } File app = new File(fileDir.getAbsolutePath() + "/" + appName); - if (app.exists() && app.length() > 0) { - logger.debug("app is existed no download!"); - return app.getAbsolutePath(); - } - String path = SpringUtils.getProperties("nk.mobile-computer.appDownloadAddr"); String server = SpringUtils.getProperties("nk.mobile-computer.serverAddr"); String url = server + path.replace("{appId}", appId); HttpUtils.downloadFileToLocal(url, fileDir.getAbsolutePath(), appName); - - if (app.length() <= 0) { - HttpUtils.downloadFileToLocal(url, fileDir.getAbsolutePath(), appName); - } - logger.info("应用[{}]下载完成,所在目录:{}。。。。。。。。。。。。。。。。。。。。", appName, app.getAbsolutePath()); - return app.getAbsolutePath(); } - protected File cutImageFile(File sourceFile,String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight){ + @Override + public File getScreenShotFile(String deviceId, Integer startX, Integer startY, int width, int height) { + File sourceFile = getScreenShotFile(deviceId); + File cutFilePath = new File(System.getProperty("user.dir") + "/tempPic"); + if (!cutFilePath.exists()) { + cutFilePath.mkdirs(); + } + File cutFile = new File(cutFilePath.getAbsolutePath() + "/" + UUID.randomUUID().toString() + ".png"); + ImgUtil.cut(sourceFile, cutFile, new java.awt.Rectangle(startX, startY, width, height)); + logger.debug("设备【{}】截取区域完成,地址:{}",deviceId,cutFile.getAbsolutePath()); + return cutFile; + } + + protected File cutImageFile(File sourceFile,String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight,int width,int height){ try { File cutFilePath = new File(System.getProperty("user.dir") + "/tempPic"); if (!cutFilePath.exists()) { cutFilePath.mkdirs(); } File cutFile = new File(cutFilePath.getAbsolutePath() + "/" + UUID.randomUUID().toString() + ".png"); - //转换比例 - PhoneEntity phoneEntity = IOSDeviceManager.getInstance().getPhoneEntity(deviceId); - int width = phoneEntity.getScreenWidth() * phoneEntity.getScale(); - int height = phoneEntity.getScreenHeight() * phoneEntity.getScale(); - int realityWidth = width; int realityHeight = height; if (width > height) { //宽高反了的情况,调换 @@ -89,14 +72,4 @@ public abstract class IosDeviceHandleHelper { throw new ExecuteException("截取图片失败"); } } - - public abstract boolean installApp(String deviceId, String appPath); - - public abstract boolean activateApp(String deviceId, String appPackage); - - public abstract File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight); - - public File getScreenShotFile(String deviceId) { - return getScreenShotFile(deviceId, null, null, null, null, null, null); - } } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/DeviceHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/DeviceHelper.java new file mode 100644 index 0000000..f74c364 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/DeviceHelper.java @@ -0,0 +1,32 @@ +package net.northking.cctp.upperComputer.utils.deviceHepler; + +import java.io.File; + +/** + * @author : yineng.huang + * @date : 2024/10/29 17:46 + */ +public interface DeviceHelper { + + String downloadAppToLocal(String appId, String appName); + + boolean cleanData(String deviceId,String appPackage); + + boolean isAppInstalled(String deviceId, String appPackage); + + boolean removeApp(String deviceId, String appPackage); + + boolean terminateApp(String deviceId, String appPackage); + + String getOldPackageCode(String deviceId, String appPackage); + + boolean installApp(String deviceId, String appPath); + + boolean activateApp(String deviceId, String appPackage); + + File getScreenShotFile(String serial); + + File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight,int width,int height); + + File getScreenShotFile(String deviceId, Integer startX, Integer startY, int width, int height); +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/android/AndroidDeviceHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/android/AndroidDeviceHelper.java new file mode 100644 index 0000000..e82dbfe --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/android/AndroidDeviceHelper.java @@ -0,0 +1,170 @@ +package net.northking.cctp.upperComputer.utils.deviceHepler.android; + +import net.northking.cctp.upperComputer.deviceManager.AndroidDeviceManager; +import net.northking.cctp.upperComputer.driver.adb.Adb; +import net.northking.cctp.upperComputer.driver.adb.AdbDevice; +import net.northking.cctp.upperComputer.driver.adb.AdbTransport; +import net.northking.cctp.upperComputer.exception.ExecuteException; +import net.northking.cctp.upperComputer.utils.deviceHepler.AbstractDeviceHelper; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * @author : yineng.huang + * @date : 2024/11/11 11:13 + */ +public class AndroidDeviceHelper extends AbstractDeviceHelper { + + private final Logger logger = LoggerFactory.getLogger(AndroidDeviceHelper.class); + + @Override + public boolean cleanData(String deviceId, String appPackage) { + boolean success = false; + logger.info("Clean data for app[{}] on device[{}]", appPackage, deviceId); + Adb adb = AndroidDeviceManager.getInstance().getAdb(); + AdbDevice adbDevice = AndroidDeviceManager.getInstance().getCurrentDevice(deviceId); + if (null == adbDevice) { + logger.error("设备【{}】不在线", deviceId); + return false; + } + AdbTransport adbTransport = adb.shell(adbDevice, String.format("pm clear %s ", appPackage)); + String line = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(adbTransport.getInputStream())); + while ((line = reader.readLine()) != null) { + logger.debug(line); + if (line.contains("Success")) { + success = true; + } + } + } catch (Exception e) { + logger.error("清除数据失败", e); + return false; + } finally { + if (null != reader) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (null != adbTransport) { + try { + adbTransport.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return success; + } + + @Override + public boolean isAppInstalled(String deviceId, String appPackage) { + return false; + } + + @Override + public boolean removeApp(String deviceId, String appPackage) { + return false; + } + + @Override + public boolean terminateApp(String deviceId, String appPackage) { + AdbDevice currentDevice = AndroidDeviceManager.getInstance().getCurrentDevice(deviceId); + if (null == currentDevice) { + throw new ExecuteException("设备不在线,无法启动app"); + } + Adb adb = AndroidDeviceManager.getInstance().getAdb(); + if (adb == null) { + try { + adb = new Adb(); + } catch (IOException e) { + logger.warn("adb通信出现错误", e); + return false; + } + } + AdbTransport shell = adb.shell(currentDevice, "am force-stop " + appPackage); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(shell.getInputStream())); + String line = null; + while (null != (line = reader.readLine())) { + logger.debug("设备【{}】关闭应用打印:{}", deviceId, line); + } + return true; + } catch (IOException e) { + logger.warn("启动app出现错误", e); + } + return false; + } + + @Override + public String getOldPackageCode(String deviceId, String appPackage) { + String oldPackageCode = ""; + String shell = "adb -s " + deviceId + " shell dumpsys package " + appPackage + " |grep versionName"; + try { + Process process = Runtime.getRuntime().exec(shell); + process.waitFor(); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = in.readLine(); + if (StringUtils.isNotBlank(line)) { + oldPackageCode = line.split("=")[1]; + } + } catch (Exception e) { + logger.error("获取应用版本失败", e); + } + logger.debug("手机应用版本为:" + oldPackageCode); + return oldPackageCode; + } + + @Override + public boolean installApp(String deviceId, String appPath) { + return false; + } + + @Override + public boolean activateApp(String deviceId, String appPackage) { + AdbDevice currentDevice = AndroidDeviceManager.getInstance().getCurrentDevice(deviceId); + if (null == currentDevice) { + throw new ExecuteException("设备不在线,无法启动app"); + } + Adb adb = AndroidDeviceManager.getInstance().getAdb(); + if (adb == null) { + try { + adb = new Adb(); + } catch (IOException e) { + logger.warn("adb通信出现错误", e); + return false; + } + } + AdbTransport shell = adb.shell(currentDevice, "monkey -p " + appPackage + " -v 1"); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(shell.getInputStream())); + String line = null; + while (null != (line = reader.readLine())) { + logger.debug("设备【{}】启动应用打印:{}", deviceId, line); + } + return true; + } catch (IOException e) { + logger.warn("启动app出现错误", e); + } + return false; + } + + @Override + public File getScreenShotFile(String serial) { + return null; + } + + @Override + public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight, int width, int height) { + return null; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/harmony/HarmonyHandlerHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/harmony/HarmonyHandlerHelper.java new file mode 100644 index 0000000..4a19abb --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/harmony/HarmonyHandlerHelper.java @@ -0,0 +1,207 @@ +package net.northking.cctp.upperComputer.utils.deviceHepler.harmony; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +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.hdc.HDCConnectStatus; +import net.northking.cctp.upperComputer.driver.harmony.hdc.HDCSession; +import net.northking.cctp.upperComputer.driver.harmony.hdc.Hdc; +import net.northking.cctp.upperComputer.exception.ExecuteException; +import net.northking.cctp.upperComputer.utils.deviceHepler.AbstractDeviceHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.File; + +/** + * @author : yineng.huang + * @date : 2024/10/29 17:44 + */ +public class HarmonyHandlerHelper extends AbstractDeviceHelper { + + private final Logger logger = LoggerFactory.getLogger(HarmonyHandlerHelper.class); + + private final String IS_APP_INSTALLED = "bm dump -n %s"; + private final String START_APP = "aa start -a %s -b %s"; + private final String TERMINATE_APP = "aa force-stop %s"; + private final String CLEAN_DATA = "bm clean -d -n %s"; + + + @Override + public boolean cleanData(String deviceId, String appPackage) { + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + HDCSession shell = Hdc.getInstance().shell(harmonyDevice.getHdcDevice(), String.format(CLEAN_DATA, appPackage)); + String result = shell.readAllLines(); + if (result != null && result.contains("successfully")) { + return true; + } else { + logger.debug("停止设备【{}】应用返回结果:{}",deviceId,result); + return false; + } + } + + @Override + public boolean isAppInstalled(String deviceId, String appPackage) { + JSONObject appInfo = getAppInfo(deviceId, appPackage); + if (null != appInfo) { + return true; + } + return false; + } + + @Override + public boolean removeApp(String deviceId, String appPackage) { + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + return Hdc.getInstance().uninstall(harmonyDevice.getHdcDevice(), appPackage, false, true); + } + + @Override + public boolean terminateApp(String deviceId, String appPackage) { + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + HDCSession shell = Hdc.getInstance().shell(harmonyDevice.getHdcDevice(), String.format(TERMINATE_APP, appPackage)); + String result = shell.readAllLines(); + if (result != null && result.contains("force stop process successfully")) { + return true; + } else { + logger.debug("停止设备【{}】应用返回结果:{}",deviceId,result); + return false; + } + } + + @Override + public String getOldPackageCode(String deviceId, String appPackage) { + JSONObject appInfo = getAppInfo(deviceId, appPackage); + if (null == appInfo) { + return null; + } else { + String versionCode = appInfo.getString("versionName"); + logger.debug("得到设备【{}】上应用【{}】的版本为:{}",deviceId,appPackage,versionCode); + return versionCode; + } + } + + @Override + public boolean installApp(String deviceId, String appPath) { + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + return Hdc.getInstance().install(harmonyDevice.getHdcDevice(),new File(appPath)); + } + + @Override + public boolean activateApp(String deviceId, String appPackage) { + JSONObject appInfo = getAppInfo(deviceId, appPackage); + if (null == appInfo) { + logger.warn("设备【{}】上不存在应用【{}】", deviceId, appPackage); + return false; + } else { + JSONArray hapModuleInfos = appInfo.getJSONArray("hapModuleInfos"); + String mainAbility = null; + if (!CollectionUtils.isEmpty(hapModuleInfos)) { + for (Object hapModuleInfo : hapModuleInfos) { + JSONObject moduleInfo = JSONObject.parseObject(JSONObject.toJSONString(hapModuleInfo)); + mainAbility = moduleInfo.getString("mainAbility"); + if (StringUtils.hasText(mainAbility)) { + break; + } + } + } + logger.warn("拿到设备【{}】应用【{}】的mainAbility为:{}", deviceId, appPackage,mainAbility); + if (null == mainAbility || StringUtils.isEmpty(mainAbility)) { + logger.warn("设备【{}】上无法找到应用【{}】的mainAbility,应用无法启动", deviceId, appPackage); + return false; + } + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + HDCSession shell = Hdc.getInstance().shell(harmonyDevice.getHdcDevice(), String.format(START_APP, mainAbility,appPackage)); + String result = shell.readAllLines(); + if (result.contains("start ability successfully")) { + return true; + } else { + logger.warn("设备【{}】上启动应用【{}】失败,执行命令的结果:{}", deviceId, appPackage,result); + return false; + } + } + } + + @Override + public File getScreenShotFile(String serial) { + HarmonyDevice harmonyDevice = getHarmonyDevice(serial); + File cutFilePath = new File(System.getProperty("user.dir") + "/tempPic"); + if (!cutFilePath.exists()) { + cutFilePath.mkdirs(); + } + String localPic = cutFilePath.getAbsolutePath() + "/" + System.currentTimeMillis() + ".png"; + File file = harmonyDevice.takeScreenshotForFile(localPic); + return file; + } + + @Override + public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight,int width,int height) { + if (cutWidth <= 0 || cutHeight <= 0) { + throw new ExecuteException("截取图片的宽高不满足要求"); + } + File snapShotFile = null; + File cutFile = null; + try { + snapShotFile = getScreenShotFile(deviceId); + if (null == snapShotFile) { + logger.debug("设备【{}】未截取到图片",deviceId); + throw new ExecuteException("未截取到图片"); + } + cutFile = cutImageFile(snapShotFile, deviceId, startX, startY, cutWidth, cutHeight, screenWidth, screenHeight,width,height); + logger.debug("设备【{}】截取到的图片为:{}",deviceId,cutFile.getAbsolutePath()); + return cutFile; + } catch (ExecuteException e) { + logger.error("截图失败", e); + throw e; + } catch (Exception e) { + logger.error("截图失败", e); + throw new ExecuteException("截取图片失败"); + }finally { + if (null != snapShotFile) { + //删除 + } + if (null != cutFile) { + //删除 + } + } + } + + private HarmonyProvider getProvider(String deviceId){ + HarmonyProvider currentDeviceProvider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(deviceId); + if (null == currentDeviceProvider) { + logger.warn("设备【{}】已经掉线了,稍后再试.......",deviceId); + throw new ExecuteException("当前设备已经掉线,请稍后再试"); + } + return currentDeviceProvider; + } + + private HarmonyDevice getHarmonyDevice(String deviceId) { + HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(deviceId); + if (null == currentDevice || currentDevice.getHdcDevice().getConnectStatus() != HDCConnectStatus.CONNECTED){ + throw new ExecuteException("当前设备已经掉线,请稍后再试"); + } + return currentDevice; + } + + private JSONObject getAppInfo(String deviceId,String appPackage){ + HarmonyDevice harmonyDevice = getHarmonyDevice(deviceId); + HDCSession shell = Hdc.getInstance().shell(harmonyDevice.getHdcDevice(), String.format(IS_APP_INSTALLED, appPackage)); + String result = shell.readAllLines(); + if (result.contains("failed to get information")) { + return null; + } else { + int index = result.indexOf("{"); + String str = result.substring(index); + JSONObject jsonObject = null; + try { + jsonObject = JSONObject.parseObject(str, JSONObject.class); + } catch (Exception e) { + logger.error("转换app信息失败,得到的结果为:{}",str); + } + return jsonObject; + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/IosDeviceHandleHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/IosDeviceHandleHelper.java new file mode 100644 index 0000000..41e923d --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/IosDeviceHandleHelper.java @@ -0,0 +1,46 @@ +package net.northking.cctp.upperComputer.utils.deviceHepler.ios; + +import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; +import net.northking.cctp.upperComputer.deviceManager.thread.IosDeviceInitThread; +import net.northking.cctp.upperComputer.driver.ios.NKAgent; +import net.northking.cctp.upperComputer.utils.ScreenShotUtils; +import net.northking.cctp.upperComputer.utils.deviceHepler.AbstractDeviceHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * @author : yineng.huang + * @date : 2024/4/24 16:44 + */ +public abstract class IosDeviceHandleHelper extends AbstractDeviceHelper { + + private final Logger logger = LoggerFactory.getLogger(IosDeviceHandleHelper.class); + + protected final String LAUNCH_APP_FAIL_FLAG = "invalid code signature"; //应用未信任启动失败标志 + + public File getScreenShotFile(String deviceId) { + return ScreenShotUtils.getIOSMobileScreenShot(deviceId); + } + + @Override + public boolean cleanData(String deviceId, String appPackage) { + logger.debug("ios设备【{}】无需清除数据...................",deviceId); + return true; + } + + @Override + public boolean terminateApp(String deviceId, String appPackage) { + IosDeviceInitThread iosInitThread = IOSDeviceManager.getInstance().getIosInitThread(deviceId); + boolean success = false; + if (null != iosInitThread && iosInitThread.isAlive() && !iosInitThread.isInterrupted()) { + NKAgent nkAgent = iosInitThread.getNkAgent(); + if (null != nkAgent) { + success = nkAgent.terminateApp(appPackage); + } + } + logger.debug("设备【{}】关闭应用【{}】结果:{}", deviceId, appPackage, success); + return success; + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/LinuxAndWindowsIosHandleHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/LinuxAndWindowsIosHandleHelper.java new file mode 100644 index 0000000..d816f8e --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/LinuxAndWindowsIosHandleHelper.java @@ -0,0 +1,239 @@ +package net.northking.cctp.upperComputer.utils.deviceHepler.ios; + +import com.alibaba.fastjson.JSONObject; +import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; +import net.northking.cctp.upperComputer.entity.PhoneEntity; +import net.northking.cctp.upperComputer.exception.ExecuteException; +import net.northking.cctp.upperComputer.utils.HttpUtils; +import net.northking.cctp.upperComputer.utils.ScreenShotUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author : yineng.huang + * @date : 2024/4/24 16:44 + */ +public class LinuxAndWindowsIosHandleHelper extends IosDeviceHandleHelper { + + private final Logger logger = LoggerFactory.getLogger(LinuxAndWindowsIosHandleHelper.class); + + @Override + public boolean isAppInstalled(String deviceId, String appPackage) { + String line = ""; + String[] cmds = {"tidevice", "-u", deviceId, "applist"}; + ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmds)).redirectErrorStream(true); + Process process = null; + try { + process = builder.start(); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + while ((line = in.readLine()) != null) { + if (line.contains(appPackage)) { + String[] split = line.split(" "); + if (split.length == 3) { + logger.debug("手机应用版本为:{}", split[2]); + return true; + } + } + } + } catch (Exception e) { + logger.error("获取应用版本失败", e); + } + return false; + } + + @Override + public boolean removeApp(String deviceId, String appPackage) { + if (isAppInstalled(deviceId, appPackage)) { + String[] cmds = {"tidevice", "-u", deviceId, "uninstall", appPackage}; + ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmds)).redirectErrorStream(true); + Process process = null; + String result = null; + try { + StringBuilder sb = new StringBuilder(); + process = builder.start(); + BufferedReader in = new BufferedReader(new InputStreamReader( + process.getInputStream())); + while (null != (result = in.readLine())) { + logger.debug("卸载app【{}】结果:{}", appPackage, result); + sb.append(result); + } + String s = sb.toString(); + if (s.endsWith("Complete")) { + logger.debug("手机应用【{}】卸载完成。。。。。。。。。。", appPackage); + return true; + } else { + return false; + } + } catch (Exception e) { + logger.error("卸载手机应用【{}】出错", appPackage, e); + return false; + } + } else { + return true; + } + } + + + @Override + public String getOldPackageCode(String deviceId, String appPackage) { + String oldPackageCode = ""; + String[] cmd = {"tidevice", "-u", deviceId, "applist"}; + try { + ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmd)).redirectErrorStream(true); + Process process = builder.start(); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = null; + while ((line = in.readLine()) != null) { + if (line.startsWith(appPackage)) { + String[] split = line.split(" "); + if (split.length == 3) { + oldPackageCode = split[2]; + return oldPackageCode; + } + } + } + + } catch (Exception e) { + logger.error("获取应用版本失败", e); + } + logger.debug("手机应用版本为:" + oldPackageCode); + return oldPackageCode; + } + + @Override + public boolean installApp(String deviceId, String appPath) { + String[] cmds = {"tidevice", "-u", deviceId, "install", appPath}; + ProcessBuilder builder = new ProcessBuilder().command(Arrays.asList(cmds)).redirectErrorStream(true); + Process process = null; + String result = null; + try { + StringBuilder sb = new StringBuilder(); + process = builder.start(); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + while (null != (result = in.readLine())) { + logger.debug("安装app【{}】结果:{}", appPath, result); + sb.append(result); + } + String s = sb.toString(); + logger.debug("打印的安装结果:{}", s); + process.waitFor(); + if (!s.contains("Fail")) { + logger.debug("手机应用【{}】安装完成。。。。。。。。。。", appPath); + return true; + } else { + return false; + } + } catch (Exception e) { + logger.error("卸载手机应用【{}】出错", appPath, e); + return false; + } + } + + @Override + public boolean activateApp(String deviceId, String appPackage) { + PhoneEntity phoneEntity = IOSDeviceManager.getInstance().getPhoneEntity(deviceId); + if (!phoneEntity.getStatus()) { + throw new ExecuteException("设备不在线,无法启动app"); + } + String wdaSessionId = IOSDeviceManager.getInstance().getPhoneWdaSessionId(deviceId); + if (wdaSessionId != null) { + String url = "http://127.0.0.1:" + phoneEntity.getPort8100() + "/session/" + wdaSessionId + "/wda/apps/state"; + Map paramMap = new HashMap<>(); + paramMap.put("bundleId", appPackage); + HttpEntity> entity = new HttpEntity<>(paramMap, null); + try { + JSONObject result = HttpUtils.doPost(new URI(url), entity, JSONObject.class); + int value = result.getIntValue("value"); + if (value == 4) { + logger.info("设备【{}】的应用【{}】已经启动并展示在前台", phoneEntity.getUdid(), appPackage); + return true; + } + } catch (URISyntaxException uriSyntaxException) { + logger.warn("设备【{}】请求应用【{}】状态时url不对", phoneEntity.getUdid(), appPackage); + } catch (Exception e) { + logger.warn("设备【{}】请求应用【{}】状态时出错,待会再试", phoneEntity.getUdid(), appPackage); + } + } +// JSONObject value = HttpUtils.doGet(new URI(url), JSONObject.class); + String[] cmd = {"tidevice", "-u", deviceId, "launch", appPackage}; + ProcessBuilder builder = new ProcessBuilder().command(cmd); + builder.redirectErrorStream(true); + try { + Process process = builder.start(); + StringBuilder sb = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = null; + while (null != (line = reader.readLine())) { + logger.debug(line); + sb.append(line); + } + String result = sb.toString(); + if (result.startsWith("PID:")) { + return true; + } + if (result.contains(LAUNCH_APP_FAIL_FLAG)) { + logger.warn("手机未信任应用开发者启动失败,放开权限支持录制信任步骤"); + logger.warn("手机启动应用异常:{}", result); + return true; + } + } catch (IOException e) { + logger.warn("启动app出现错误", e); + } + return false; + } + + /** + * 截取区域图片,得到文件 + * + * @param deviceId + * @param startX + * @param startY + * @param cutWidth + * @param cutHeight + * @param screenWidth + * @param screenHeight + * @return + */ + @Override + public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight,int width, int height) { + if (cutWidth <= 0 || cutHeight <= 0) { + throw new ExecuteException("截取图片的宽高不满足要求"); + } + File snapShotFile = null; + File cutFile = null; + try { + snapShotFile = ScreenShotUtils.getIOSMobileScreenShot(deviceId); + if (null == snapShotFile) { + logger.debug("设备【{}】未截取到图片",deviceId); + throw new ExecuteException("未截取到图片"); + } + cutFile = cutImageFile(snapShotFile, deviceId, startX, startY, cutWidth, cutHeight, screenWidth, screenHeight,width,height); + logger.debug("设备【{}】截取到的图片为:{}",deviceId,cutFile.getAbsolutePath()); + return cutFile; + } catch (ExecuteException e) { + logger.error("截图失败", e); + throw e; + } catch (Exception e) { + logger.error("截图失败", e); + throw new ExecuteException("截取图片失败"); + }finally { + if (null != snapShotFile) { + //删除 + } + if (null != cutFile) { + //删除 + } + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/MacIosHandleHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/MacIosHandleHelper.java similarity index 58% rename from cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/MacIosHandleHelper.java rename to cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/MacIosHandleHelper.java index 988b5b8..9d6fa06 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/MacIosHandleHelper.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/deviceHepler/ios/MacIosHandleHelper.java @@ -1,7 +1,5 @@ -package net.northking.cctp.upperComputer.utils.ios; +package net.northking.cctp.upperComputer.utils.deviceHepler.ios; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager; import net.northking.cctp.upperComputer.deviceManager.common.PyMobileDevice; @@ -9,21 +7,14 @@ import net.northking.cctp.upperComputer.deviceManager.entity.AppleApplicationInf import net.northking.cctp.upperComputer.deviceManager.thread.IosDeviceInitThread; import net.northking.cctp.upperComputer.driver.ios.NKAgent; import net.northking.cctp.upperComputer.driver.ios.command.data.ScreenShotData; -import net.northking.cctp.upperComputer.entity.PhoneEntity; import net.northking.cctp.upperComputer.exception.ExecuteException; -import net.northking.cctp.upperComputer.utils.HttpUtils; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; import java.util.HashMap; -import java.util.Map; import java.util.UUID; /** @@ -39,13 +30,13 @@ public class MacIosHandleHelper extends IosDeviceHandleHelper { PyMobileDevice.SpecificAppleDeviceInfo specificAppleDeviceInfo = IOSDeviceManager.getInstance().getSpecificAppleDeviceInfo(deviceId); HashMap appMap = PyMobileDevice.getInstance().listApp(specificAppleDeviceInfo, false, true); if (null == appMap) { - logger.warn("没有在设备【{}】上找到app【{}】信息",deviceId,appPackage); + logger.warn("没有在设备【{}】上找到app【{}】信息", deviceId, appPackage); return false; } boolean contain = appMap.containsKey(appPackage); if (contain) { AppleApplicationInfo appleApplicationInfo = appMap.get(appPackage); - logger.debug("设备【{}】上的app信息:{}",deviceId,JSON.toJSONString(appleApplicationInfo)); + logger.debug("设备【{}】上的app信息:{}", deviceId, appleApplicationInfo.getCFBundleShortVersionString()); return true; } return false; @@ -55,25 +46,26 @@ public class MacIosHandleHelper extends IosDeviceHandleHelper { public boolean removeApp(String deviceId, String appPackage) { PyMobileDevice.SpecificAppleDeviceInfo specificAppleDeviceInfo = IOSDeviceManager.getInstance().getSpecificAppleDeviceInfo(deviceId); boolean success = PyMobileDevice.getInstance().uninstallApp(specificAppleDeviceInfo, appPackage, null); - logger.debug("设备【{}】卸载app【{}】的结果:{}",deviceId,appPackage,success); + logger.debug("设备【{}】卸载app【{}】的结果:{}", deviceId, appPackage, success); return success; } + @Override public String getOldPackageCode(String deviceId, String appPackage) { PyMobileDevice.SpecificAppleDeviceInfo specificAppleDeviceInfo = IOSDeviceManager.getInstance().getSpecificAppleDeviceInfo(deviceId); HashMap appMap = PyMobileDevice.getInstance().listApp(specificAppleDeviceInfo, false, true); if (null == appMap) { - logger.warn("没有在设备【{}】上找到app【{}】信息",deviceId,appPackage); + logger.warn("没有在设备【{}】上找到app【{}】信息", deviceId, appPackage); return null; } boolean contain = appMap.containsKey(appPackage); if (contain) { AppleApplicationInfo appleApplicationInfo = appMap.get(appPackage); - logger.debug("设备【{}】上的app信息:{}",deviceId,JSON.toJSONString(appleApplicationInfo)); + logger.debug("设备【{}】上的app信息:{}", deviceId, appleApplicationInfo.getCFBundleShortVersionString()); return appleApplicationInfo.getCFBundleShortVersionString(); } else { - logger.warn("没有在设备【{}】上找到app【{}】信息",deviceId,appPackage); + logger.warn("没有在设备【{}】上找到app【{}】信息", deviceId, appPackage); } return null; } @@ -82,46 +74,33 @@ public class MacIosHandleHelper extends IosDeviceHandleHelper { public boolean installApp(String deviceId, String appPath) { PyMobileDevice.SpecificAppleDeviceInfo specificAppleDeviceInfo = IOSDeviceManager.getInstance().getSpecificAppleDeviceInfo(deviceId); boolean success = PyMobileDevice.getInstance().installApp(specificAppleDeviceInfo, appPath, null); - logger.debug("设备【{}】安装app【{}】的结果:{}",deviceId,appPath,success); + logger.debug("设备【{}】安装app【{}】的结果:{}", deviceId, appPath, success); return success; } @Override public boolean activateApp(String deviceId, String appPackage) { - boolean success = false; - int times = 0; - while (!success && times < 3) { - try { - NKAgent nkAgent = getNkAgent(deviceId); - String bundleId = nkAgent.activeAppBundleId(); - logger.info("设备【{}】的当前启动的应用为【{}】,传过来要启动的应用为【{}】", deviceId, bundleId, appPackage); - if (StringUtils.equals(bundleId, appPackage)) { - logger.info("设备【{}】的应用【{}】已经启动并展示在前台", deviceId, appPackage); - success = true; - } else { - logger.info("设备【{}】的应用【{}】未展示在前台,开始启动", deviceId, appPackage); - success = nkAgent.launchApp(appPackage); - logger.debug("设备【{}】启动app【{}】的结果:{}", deviceId, appPackage, success); - } - - } catch (Exception e) { - logger.error("启动应用失败,e:", e); - } - if (!success) { - try { - // 如果失败是因为agent失效,等待重新创建 - Thread.sleep(5000L); - } catch (InterruptedException e) { - logger.error("启动app休眠打断。"); - } - } - times++; + NKAgent nkAgent = getNkAgent(deviceId); + String bundleId = nkAgent.activeAppBundleId(); + logger.info("设备【{}】的当前启动的应用为【{}】,传过来要启动的应用为【{}】", deviceId, bundleId, appPackage); + if (bundleId.equals(appPackage)) { + logger.info("设备【{}】的应用【{}】已经启动并展示在前台", deviceId, appPackage); + return true; } + logger.info("设备【{}】的应用【{}】未展示在前台,开始启动", deviceId, appPackage); + boolean success = nkAgent.launchApp(appPackage); + logger.debug("设备【{}】启动app【{}】的结果:{}", deviceId, appPackage, success); return success; } + + + @Override - public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight) { + public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight,int width, int height) { + if (cutWidth <= 0 || cutHeight <= 0) { + throw new ExecuteException("截取图片的宽高不满足要求"); + } int screenShotTime = 1; while (screenShotTime <= 3) { logger.debug("设备【{}】第{}次截图开始.................", deviceId, screenShotTime); @@ -141,16 +120,8 @@ public class MacIosHandleHelper extends IosDeviceHandleHelper { fileOutputStream = new FileOutputStream(screenShotFile); fileOutputStream.write(screenShotData.data); fileOutputStream.flush(); - logger.debug("设备【{}】第{}次截图在磁盘的位置为:{}", deviceId, screenShotTime, screenShotFile.getAbsolutePath()); - if (null != startX && null != startY) { - if (cutWidth <= 0 || cutHeight <= 0) { - throw new ExecuteException("截取图片的宽高不满足要求"); - } - file = cutImageFile(screenShotFile, deviceId, startX, startY, cutWidth, cutHeight, screenWidth, screenHeight); - logger.debug("设备【{}】截取到的图片为:{}",deviceId,file.getAbsolutePath()); - } else { - file = screenShotFile; - } + logger.debug("设备【】第{}次截图在磁盘的位置为:{}", deviceId, screenShotTime, screenShotFile.getAbsolutePath()); + file = cutImageFile(screenShotFile, deviceId, startX, startY, cutWidth, cutHeight, screenWidth, screenHeight,width,height); logger.debug("截取出来的图像为:{}", file.getAbsolutePath()); return file; } catch (IOException e) { @@ -182,32 +153,13 @@ public class MacIosHandleHelper extends IosDeviceHandleHelper { } private NKAgent getNkAgent(String deviceId) { - int times = 0; - NKAgent nkAgent = null; - while (nkAgent == null && times < 3) { - IosDeviceInitThread iosInitThread = IOSDeviceManager.getInstance().getIosInitThread(deviceId); - if (null == iosInitThread || !iosInitThread.isAlive() || iosInitThread.isInterrupted()) { - throw new ExecuteException("设备已经掉线,请重新接入设备"); - } - try { - nkAgent = iosInitThread.getNkAgent(); - if (!nkAgent.getStatus()) { - throw new ExecuteException("设备Agent还未准备好,稍后再试"); - } - } catch (Exception e) { - logger.error("创建nkAgent失败,e", e); - nkAgent = null; - } - if (nkAgent == null) { - try { - // 如果失败是因为agent失效,等待重新创建 - logger.debug("nkAgent重试创建{}次", times); - Thread.sleep(5000L); - } catch (InterruptedException e) { - logger.error("启动app休眠打断。"); - } - } - times++; + IosDeviceInitThread iosInitThread = IOSDeviceManager.getInstance().getIosInitThread(deviceId); + if (null == iosInitThread || !iosInitThread.isAlive() || iosInitThread.isInterrupted()) { + throw new ExecuteException("设备已经掉线,请重新接入设备"); + } + NKAgent nkAgent = iosInitThread.getNkAgent(); + if (!nkAgent.getStatus()) { + throw new ExecuteException("设备Agent还未准备好,稍后再试"); } return nkAgent; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/LinuxAndWindowsIosHandleHelper.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/LinuxAndWindowsIosHandleHelper.java deleted file mode 100644 index e8cd400..0000000 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/utils/ios/LinuxAndWindowsIosHandleHelper.java +++ /dev/null @@ -1,301 +0,0 @@ -package net.northking.cctp.upperComputer.utils.ios; - -import com.alibaba.fastjson.JSONObject; -import net.northking.cctp.upperComputer.deviceManager.IOSDeviceManager; -import net.northking.cctp.upperComputer.deviceManager.UpperComputerManager; -import net.northking.cctp.upperComputer.deviceManager.thread.IosDeviceInitThread; -import net.northking.cctp.upperComputer.driver.ios.NKAgent; -import net.northking.cctp.upperComputer.driver.ios.command.data.ScreenShotData; -import net.northking.cctp.upperComputer.entity.PhoneEntity; -import net.northking.cctp.upperComputer.exception.ExecuteException; -import net.northking.cctp.upperComputer.utils.HttpUtils; -import net.northking.cctp.upperComputer.utils.ScreenShotUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; - -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * @author : yineng.huang - * @date : 2024/4/24 16:44 - */ -public class LinuxAndWindowsIosHandleHelper extends IosDeviceHandleHelper { - - private final Logger logger = LoggerFactory.getLogger(LinuxAndWindowsIosHandleHelper.class); - - @Override - public boolean isAppInstalled(String deviceId, String appPackage) { - boolean success = false; - int count = 0; - do { - String line = ""; - String[] cmds = {"tidevice", "-u", deviceId, "applist"}; - ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmds)).redirectErrorStream(true); - Process process = null; - try { - process = builder.start(); - BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); - while ((line = in.readLine()) != null) { - if (line.contains(appPackage)) { - String[] split = line.split(" "); - if (split.length == 3) { - logger.debug("手机应用版本为:{}", split[2]); - success = true; - } - } - } - } catch (Exception e) { - logger.error("获取应用版本失败", e); - success = false; - } - count++; - } while (!success && count < 3); - return success; - } - - @Override - public boolean removeApp(String deviceId, String appPackage) { - boolean success = false; - int count = 0; - do { - if (isAppInstalled(deviceId, appPackage)) { - String[] cmds = {"tidevice", "-u", deviceId, "uninstall", appPackage}; - ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmds)).redirectErrorStream(true); - Process process = null; - String result = null; - try { - StringBuilder sb = new StringBuilder(); - process = builder.start(); - BufferedReader in = new BufferedReader(new InputStreamReader( - process.getInputStream())); - while (null != (result = in.readLine())) { - logger.debug("卸载app【{}】结果:{}", appPackage, result); - sb.append(result); - } - String s = sb.toString(); - if (s.endsWith("Complete")) { - logger.debug("手机应用【{}】卸载完成。。。。。。。。。。", appPackage); - success = true; - } else { - success = false; - } - } catch (Exception e) { - logger.error("卸载手机应用【{}】出错", appPackage, e); - success = false; - } - } else { - success = true; - } - count++; - } while (!success && count < 3); - return success; - } - - @Override - public String getOldPackageCode(String deviceId, String appPackage) { - String oldPackageCode = ""; - String[] cmd = {"tidevice", "-u", deviceId, "applist"}; - try { - ProcessBuilder builder = new ProcessBuilder(Arrays.asList(cmd)).redirectErrorStream(true); - Process process = builder.start(); - BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line = null; - while ((line = in.readLine()) != null) { - if (line.startsWith(appPackage)) { - String[] split = line.split(" "); - if (split.length == 3) { - oldPackageCode = split[2]; - return oldPackageCode; - } - } - } - - } catch (Exception e) { - logger.error("获取应用版本失败", e); - } - logger.debug("手机应用版本为:" + oldPackageCode); - return oldPackageCode; - } - - @Override - public boolean installApp(String deviceId, String appPath) { - boolean success = false; - int count = 0; - do { - String[] cmds = {"tidevice", "-u", deviceId, "install", appPath}; - ProcessBuilder builder = new ProcessBuilder().command(Arrays.asList(cmds)).redirectErrorStream(true); - Process process = null; - String result = null; - try { - StringBuilder sb = new StringBuilder(); - process = builder.start(); - BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); - while (null != (result = in.readLine())) { - logger.debug("安装app【{}】结果:{}", appPath, result); - sb.append(result); - } - String s = sb.toString(); - logger.debug("打印的安装结果:{}", s); - process.waitFor(); - if (!s.contains("Fail")) { - logger.debug("手机应用【{}】安装完成。。。。。。。。。。", appPath); - success = true; - } else { - success = false; - } - } catch (Exception e) { - logger.error("卸载手机应用【{}】出错", appPath, e); - success = false; - } - count++; - } while (!success && count < 3); - return success; - } - - @Override - public boolean activateApp(String deviceId, String appPackage) { - boolean success = false; - int times = 0; - while (!success && times < 3) { - try { - NKAgent nkAgent = getNkAgent(deviceId); - String bundleId = nkAgent.activeAppBundleId(); - logger.info("设备【{}】的当前启动的应用为【{}】,传过来要启动的应用为【{}】", deviceId, bundleId, appPackage); - if (StringUtils.equals(bundleId, appPackage)) { - logger.info("设备【{}】的应用【{}】已经启动并展示在前台", deviceId, appPackage); - success = true; - } else { - logger.info("设备【{}】的应用【{}】未展示在前台,开始启动", deviceId, appPackage); - success = nkAgent.launchApp(appPackage); - logger.debug("设备【{}】启动app【{}】的结果:{}", deviceId, appPackage, success); - } - - } catch (Exception e) { - logger.error("启动应用失败,e:", e); - } - if (!success) { - try { - // 如果失败是因为agent失效,等待重新创建 - Thread.sleep(5000L); - } catch (InterruptedException e) { - logger.error("启动app休眠打断。"); - } - } - times++; - } - return success; - } - - /** - * 截取区域图片,得到文件 - * 如果x y没有值,则返回全屏图片 - * - * @param deviceId - * @param startX - * @param startY - * @param cutWidth - * @param cutHeight - * @param screenWidth - * @param screenHeight - * @return - */ - @Override - public File getScreenShotFile(String deviceId, Integer startX, Integer startY, Integer cutWidth, Integer cutHeight, Integer screenWidth, Integer screenHeight) { - int screenShotTime = 1; - while (screenShotTime <= 3) { - logger.debug("设备【{}】第{}次截图开始.................", deviceId, screenShotTime); - NKAgent nkAgent = getNkAgent(deviceId); - ScreenShotData screenShotData = nkAgent.screenShot(); - logger.debug("设备【{}】第{}次截图的大小:{}", deviceId, screenShotTime, screenShotData.dataLength); - if (screenShotData.dataLength > 0) { - File file = null; - File screenShotFile = null; - FileOutputStream fileOutputStream = null; - try { - File picDir = new File(UpperComputerManager.getInstance().getApplicationPath() + "/tempPic"); - if (!picDir.exists()) { - picDir.mkdirs(); - } - screenShotFile = new File(picDir.getAbsolutePath() + "/" + UUID.randomUUID() + ".jpg"); - fileOutputStream = new FileOutputStream(screenShotFile); - fileOutputStream.write(screenShotData.data); - fileOutputStream.flush(); - logger.debug("设备【{}】第{}次截图在磁盘的位置为:{}", deviceId, screenShotTime, screenShotFile.getAbsolutePath()); - if (null != startX && null != startY) { - if (cutWidth <= 0 || cutHeight <= 0) { - throw new ExecuteException("截取图片的宽高不满足要求"); - } - file = cutImageFile(screenShotFile, deviceId, startX, startY, cutWidth, cutHeight, screenWidth, screenHeight); - logger.debug("设备【{}】截取到的图片为:{}",deviceId,file.getAbsolutePath()); - } else { - file = screenShotFile; - } - logger.debug("截取出来的图像为:{}", file.getAbsolutePath()); - return file; - } catch (IOException e) { - logger.error("截图写入磁盘失败", e); - } finally { - if (null != fileOutputStream) { - try { - fileOutputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - if (null != screenShotFile) { - //删除 - } - if (null != file) { - //删除 - } - } - } - screenShotTime++; - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - throw new ExecuteException("设备截图失败"); - } - - private NKAgent getNkAgent(String deviceId) { - int times = 0; - NKAgent nkAgent = null; - while (nkAgent == null && times < 3) { - IosDeviceInitThread iosInitThread = IOSDeviceManager.getInstance().getIosInitThread(deviceId); - if (null == iosInitThread || !iosInitThread.isAlive() || iosInitThread.isInterrupted()) { - throw new ExecuteException("设备已经掉线,请重新接入设备"); - } - try { - nkAgent = iosInitThread.getNkAgent(); - if (!nkAgent.getStatus()) { - throw new ExecuteException("设备Agent还未准备好,稍后再试"); - } - } catch (Exception e) { - logger.error("创建nkAgent失败,e", e); - nkAgent = null; - } - if (nkAgent == null) { - try { - // 如果失败是因为agent失效,等待重新创建 - logger.debug("nkAgent重试创建{}次", times); - Thread.sleep(5000L); - } catch (InterruptedException e) { - logger.error("启动app休眠打断。"); - } - } - times++; - } - return nkAgent; - } -} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/DeviceConnectionWebSocket.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/DeviceConnectionWebSocket.java index d91a8cb..4f898e2 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/DeviceConnectionWebSocket.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/DeviceConnectionWebSocket.java @@ -2,6 +2,7 @@ package net.northking.cctp.upperComputer.webSocket; import com.alibaba.fastjson.JSON; 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.service.DeviceConnectionService; import net.northking.cctp.upperComputer.utils.SpringUtils; @@ -77,6 +78,8 @@ public class DeviceConnectionWebSocket { return AndroidDeviceManager.getInstance().checkMobileIsOnline(serial); } else if ("ios".equals(type)){ return IOSDeviceManager.getInstance().checkMobileIsOnline(serial); + }else if ("harmony".equals(type)) { + return HarmonyDeviceManager.getInstance().checkMobileIsOnline(serial); } return false; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/entity/Point.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/entity/Point.java index c215b37..b15ec44 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/entity/Point.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/entity/Point.java @@ -14,7 +14,7 @@ public class Point { public Point() { } - public float getX() { + public Float getX() { return x; } @@ -22,7 +22,7 @@ public class Point { this.x = x; } - public float getY() { + public Float getY() { return y; } diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AbstractIosMessageHandlerThread.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AbstractIosMessageHandlerThread.java index f5a254c..96ef9c1 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AbstractIosMessageHandlerThread.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AbstractIosMessageHandlerThread.java @@ -508,7 +508,7 @@ public abstract class AbstractIosMessageHandlerThread extends AbstractMessageHan SessionUtils.sendSuccessData(session, request, null, "滑动成功"); } - private void initDeviceRequireParam() throws ParamMistakeException { + public void initDeviceRequireParam() throws ParamMistakeException { logger.info("获取手机【{}】的实际宽高............................", phoneEntity.getUdid()); catchParam.setRealWidth(phoneEntity.getScreenWidth() * phoneEntity.getScale()); catchParam.setRealHeight(phoneEntity.getScreenHeight() * phoneEntity.getScale()); diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AndroidMessageHandlerThread.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AndroidMessageHandlerThread.java index ce1c522..cd8921f 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AndroidMessageHandlerThread.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/AndroidMessageHandlerThread.java @@ -1057,7 +1057,7 @@ public class AndroidMessageHandlerThread extends AbstractMessageHandler { SessionUtils.sendSuccessData(session, request, null, "滑动成功"); } - private void initDeviceRequireParam() throws ParamMistakeException { + public void initDeviceRequireParam() throws ParamMistakeException { logger.info("获取手机【{}】的实际宽高............................",this.serial); AndroidDeviceInitThread androidInitThread = AndroidDeviceManager.getInstance().getAndroidInitThread(adbDevice.getSerial()); if (null == androidInitThread) { diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyGetNodeTreeThread.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyGetNodeTreeThread.java new file mode 100644 index 0000000..5211746 --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyGetNodeTreeThread.java @@ -0,0 +1,163 @@ +package net.northking.cctp.upperComputer.webSocket.thread; + +import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager; +import net.northking.cctp.upperComputer.deviceManager.thread.HarmonyProvider; +import net.northking.cctp.upperComputer.driver.harmony.ui.UiComponent; +import net.northking.cctp.upperComputer.utils.SessionUtils; +import net.northking.cctp.upperComputer.webSocket.entity.CmdRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.websocket.Session; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * @author : yineng.huang + * @date : 2024/4/25 16:22 + */ +public class HarmonyGetNodeTreeThread extends Thread { + + private final Logger logger = LoggerFactory.getLogger(HarmonyGetNodeTreeThread.class); + + private Session session; + + private String deviceId; + + private CmdRequest request; + + private boolean keepPushNode = true; //前端是否打开了获取ui节点 + + private final Object lock = new Object(); + + + public HarmonyGetNodeTreeThread(Session session, String deviceId, CmdRequest request) { + this.session = session; + this.deviceId = deviceId; + this.request = request; + this.keepPushNode = true; + setName(deviceId + "】node节点"); + } + + @Override + public void run() { + logger.info("开始获取设备【{}】的节点............................", deviceId); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = null; + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + logger.error("Document创建失败",e); + return; + } + Map data = new HashMap<>(); + while (!isInterrupted()) { + if (keepPushNode) { + try { + HarmonyProvider harmonyProvider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(this.deviceId); + if (null == harmonyProvider) { + logger.debug("设备【{}】掉线了,等一下,不要急........................", deviceId); + break; + } + UiComponent uiComponent = harmonyProvider.captureLayout(); + logger.debug("获取结束,nodeData空:{}", uiComponent == null); + if (null != uiComponent) { + Document document = builder.newDocument(); + Node xmlElement = uiComponent.createXMLElement(document); + document.appendChild(xmlElement); + String xml = toXMLString(document); + data.put("data", xml); + SessionUtils.sendContinuousData(session, request, data, "获取节点树成功"); + } else { + logger.debug("获取结束,nodeData为空,等会再重试"); + } + } catch (IllegalStateException e) { + logger.warn("webSession已经关闭",e); + break; + } catch (Exception e) { + logger.warn("设备【{}】获取节点出了点问题,但是不影响,继续", deviceId, e); + } + synchronized (lock) { + logger.warn("设备【{}】等待下一次获取node节点.....................", deviceId); + try { + lock.wait(1*1000); + } catch (InterruptedException e) { + interrupt(); + break; + } + } + } else { + synchronized (lock) { + logger.warn("设备【{}】等待获取node节点.....................", deviceId); + try { + lock.wait(); + } catch (InterruptedException e) { + interrupt(); + break; + } + } + } + } + logger.info("设备【{}】获取node节点线程退出...............", deviceId); + } + + public CmdRequest getRequest() { + return request; + } + + public void setRequest(CmdRequest request) { + this.request = request; + } + + + public void stopGetNodeTree(CmdRequest request) { + SessionUtils.sendSuccessData(session, this.request, null, "即将停止获取节点树........"); + SessionUtils.sendSuccessData(session, request, null, "停止获取节点树成功........"); + this.keepPushNode = false; + } + + public void startGetNodeTree(CmdRequest request) { + this.request = request; + this.keepPushNode = true; + synchronized (lock) { + logger.debug("设备【{}】开始获取节点树...................", deviceId); + lock.notify(); + } + } + + + public void exitGetNodeTree() { + interrupt(); + } + + public void getOnceNodeTree() { + if (keepPushNode) { + synchronized (lock) { + logger.debug("设备【{}】操作了,获取一次节点树...................", deviceId); + lock.notify(); + } + } else { + logger.warn("设备【{}】前端已经关闭了uiNode,无需获取.............", deviceId); + } + } + + String toXMLString(Document document) throws TransformerException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + transformer.transform(new DOMSource(document), result); + return writer.toString(); + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyMessageHandlerThread.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyMessageHandlerThread.java new file mode 100644 index 0000000..29ed12e --- /dev/null +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/HarmonyMessageHandlerThread.java @@ -0,0 +1,595 @@ +package net.northking.cctp.upperComputer.webSocket.thread; + +import com.alibaba.fastjson.JSON; +import net.northking.cctp.upperComputer.constants.AndroidHotKeyCodeEnum; +import net.northking.cctp.upperComputer.constants.HarmonyKeyBoardCodeEnum; +import net.northking.cctp.upperComputer.constants.KeyBoardCodeEnum; +import net.northking.cctp.upperComputer.constants.ResponseCmd; +import net.northking.cctp.upperComputer.deviceManager.HarmonyDeviceManager; +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.HarmonyKeyCode; +import net.northking.cctp.upperComputer.driver.harmony.hyppium.data.DisplaySize; +import net.northking.cctp.upperComputer.exception.ExecuteException; +import net.northking.cctp.upperComputer.exception.ParamMistakeException; +import net.northking.cctp.upperComputer.utils.SessionUtils; +import net.northking.cctp.upperComputer.webSocket.entity.CmdRequest; +import net.northking.cctp.upperComputer.webSocket.entity.ParamCheck; +import net.northking.cctp.upperComputer.webSocket.entity.Point; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.websocket.Session; +import java.util.*; + +/** + * @author : yineng.huang + * @date : 2024/10/22 15:29 + */ +public class HarmonyMessageHandlerThread extends AbstractMessageHandler{ + + private final Logger logger = LoggerFactory.getLogger(HarmonyMessageHandlerThread.class); + + private HarmonyDevice harmonyDevice; + + private boolean shiftDown = false; + + private boolean ctrlDown = false; + + private boolean altDown = false; + + private boolean capsDown = false; + + private Point lastClickPoint; + + private HarmonyGetNodeTreeThread nodeTreeThread; + + private List capsUpLetters = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"); + + private List capsDownLetters = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"); + + + public HarmonyMessageHandlerThread(HarmonyDevice currentDevice, Session session){ + this.harmonyDevice = currentDevice; + this.session = session; + this.serial = currentDevice.getHdcDevice().getConnectKey(); + setName("[" + serial + "-handleMsg]"); + initDeviceRequireParam(); + logger.info("设备【{}】的消息处理线程初始化完毕..........................", this.serial); + } + + @Override + public void stopAndExit() { + logger.info("收到关闭手机【{}】连接的指令.......................",this.serial); + HarmonyDeviceManager.getInstance().stopWebScreen(this.serial,session); + cmdQueues.clear(); + interrupt(); + } + + @Override + public void handleScreenOn(CmdRequest request) { + Map data = request.getData(); + ParamCheck width = ParamCheck.build().name("width").type("int").withMin(0).withMax(catchParam.getRealWidth()); + ParamCheck height = ParamCheck.build().name("height").type("int").withMin(0).withMax(catchParam.getRealHeight()); + ParamCheck quality = ParamCheck.build().name("quality").type("int").withMin(0).withMax(100); + ParamCheck framerate = ParamCheck.build().name("framerate").type("int").withMin(0).withMax(100); + ParamCheck rotation = ParamCheck.build().name("rotation").type("int").rangeSelection().withIntValue(Arrays.asList(0, 1, 2, 3)); + Map params = null; + try { + params = checkParam(data, false, width, height, quality, framerate, rotation); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + int widthUse = (int) params.get("width"); + catchParam.setWidth(widthUse); + int heightUse = (int) params.get("height"); + catchParam.setHeight(heightUse); + int qualityUse = (int) params.get("quality"); + catchParam.setQuality(qualityUse); + int frameRateUse = (int) params.get("framerate"); + catchParam.setFrameRate(frameRateUse); + int rotationUse = (int) params.get("rotation"); + catchParam.setRotation(rotationUse); + logger.debug("开启屏幕=====>width:{} height:{} quality:{} frameRate:{},rotation:{}", widthUse, heightUse, qualityUse, frameRateUse, rotationUse); + HarmonyProvider harmonyProvider = getHarmonyProvider(); + HarmonyScreenResponseThread harmonyScreenResponseThread = HarmonyDeviceManager.getInstance().getScreenThread(serial); + if (null == harmonyScreenResponseThread) { + harmonyScreenResponseThread = new HarmonyScreenResponseThread(session, harmonyDevice); + HarmonyDeviceManager.getInstance().saveScreenThread(serial, harmonyScreenResponseThread); + //开启手机端的屏幕 + boolean success = harmonyProvider.startCaptureScreenImageStream(0.5f, harmonyScreenResponseThread); + if (success) { + harmonyScreenResponseThread.setScreenOnRequest(session, request); + harmonyScreenResponseThread.startSendScreenToWeb(session, catchParam); + logger.info("开启设备【{}】的图片流成功", this.serial); + } else { + logger.info("开启设备【{}】的图片流失败", this.serial); + Map result = new HashMap<>(); + result.put(ResponseCmd.DeviceStatus.STATUS, ResponseCmd.DeviceStatus.DISCONNECT); + SessionUtils.sendMessageInitiative(session, ResponseCmd.DEVICE_STATUS, this.serial, result, "开启设备的图片流失败,正在重试"); + } + } else { + logger.info("设备【{}】的屏幕已经开启了,通知往前端发即可", this.serial); + harmonyScreenResponseThread.setScreenOnRequest(session, request); + harmonyScreenResponseThread.startSendScreenToWeb(session, catchParam); + } + + + } + + @Override + public void changeScreenSize(CmdRequest request) { + + } + + @Override + public void changeScreenQuality(CmdRequest request) { + + } + + + @Override + public void screenShot(CmdRequest request) { + + } + + @Override + public void touchDown(CmdRequest request) { + Map data = request.getData(); + ParamCheck xCheck = ParamCheck.build().name("x").type("double").withMin(0).withMax(catchParam.getWidth()); + ParamCheck yCheck = ParamCheck.build().name("y").type("double").withMin(0).withMax(catchParam.getHeight()); + Map params = null; + try { + params = checkParam(data, false, xCheck, yCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + Point point = calculatePoint(params); + lastClickPoint = point; + HarmonyProvider harmonyProvider = getHarmonyProvider(); + boolean success = harmonyProvider.touchDown(point.getX().intValue(), point.getY().intValue()); + if (success) { + SessionUtils.sendSuccessData(session, request, null, "摁下成功"); + } else { + logger.warn("设备【{}】摁下失败............................",this.serial); + } + } + + @Override + public void touchMove(CmdRequest request) { + Map data = request.getData(); + ParamCheck xCheck = ParamCheck.build().name("x").type("double").withMin(0).withMax(catchParam.getWidth()); + ParamCheck yCheck = ParamCheck.build().name("y").type("double").withMin(0).withMax(catchParam.getHeight()); + Map params = null; + try { + params = checkParam(data, false, xCheck, yCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + Point point = calculatePoint(params); + HarmonyProvider harmonyProvider = getHarmonyProvider(); + boolean success = harmonyProvider.touchMove(point.getX().intValue(), point.getY().intValue()); + if (success) { + SessionUtils.sendSuccessData(session, request, null, "滑动成功"); + } else { + logger.warn("设备【{}】滑动失败............................",this.serial); + } + + } + + @Override + public void touchUp(CmdRequest request) { + Map data = request.getData(); + ParamCheck xCheck = ParamCheck.build().name("x").type("double").withMin(0).withMax(catchParam.getWidth()); + ParamCheck yCheck = ParamCheck.build().name("y").type("double").withMin(0).withMax(catchParam.getHeight()); + Map params = null; + try { + params = checkParam(data, false, xCheck, yCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + Point point = calculatePoint(params); + HarmonyProvider harmonyProvider = getHarmonyProvider(); + boolean success = harmonyProvider.touchUp(point.getX().intValue(), point.getY().intValue()); + if (success) { + SessionUtils.sendSuccessData(session, request, null, "抬起成功"); + } else { + logger.warn("设备【{}】抬起失败............................",this.serial); + } + } + + @Override + public void inputKeyPress(CmdRequest request) { + Map data = request.getData(); + ParamCheck keyCodeCheck = ParamCheck.build().name("key").type("string"); + Map params = null; + try { + params = checkParam(data, false, keyCodeCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + String key = (String) params.get("key"); + int code = AndroidHotKeyCodeEnum.getCode(key); + if (code < 0) { + SessionUtils.sendFailureMessage(session, request, "热键[" + key + "]不存在"); + return; + } + HarmonyProvider harmonyProvider = getHarmonyProvider(); + boolean success = false; + switch (key) { + case "home": + success = harmonyProvider.pressHome(); + break; + case "appSwitch": + success = harmonyProvider.pressRecentApp(); + break; + case "back": + success = harmonyProvider.pressBack(); + break; + case "volumeUp": + success = harmonyProvider.pressUpVolume(); + break; + case "volumeDown": + success = harmonyProvider.pressDownVolume(); + break; + case "power": + success = harmonyProvider.pressPowerKey(); + break; + default: + logger.warn("没有对应的热键完成对设备的操控"); + break; + } + if (success) { + SessionUtils.sendSuccessData(session, request, null, "热键处理成功"); + } else { + logger.warn("设备【{}】热键【{}】操控失败............................",this.serial,key); + } + } + + @Override + public void inputType(CmdRequest request) { + + } + + @Override + public void inputSetText(CmdRequest request) { + Map data = request.getData(); + ParamCheck textCheck = ParamCheck.build().name("text").type("string").length(Integer.MAX_VALUE); + ParamCheck clearCheck = ParamCheck.build().name("clear").type("boolean"); + Map params = null; + try { + params = checkParam(data, false, textCheck, clearCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + String text = (String) params.get("text"); + boolean clear = (boolean) params.get("clear"); + HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(this.serial); + if (null != currentDevice) { + boolean success = false; + if (null != lastClickPoint) { + success = harmonyDevice.inputText(lastClickPoint.getX().intValue(), lastClickPoint.getY().intValue(), text); + } + if (success) { + SessionUtils.sendSuccessData(session, request, null, "输入文本成功"); + } else { + logger.warn("设备【{}】输入文本失败............................",this.serial); + } + } else { + SessionUtils.sendFailureMessage(session, request, "设备掉线,输入文本失败"); + } + + } + + @Override + public void clipboardPast(CmdRequest request) { + + } + + @Override + public void clipboardCopy(CmdRequest request) { + + } + + @Override + public void rotationScreen(CmdRequest request) { + + } + + @Override + public void restartDevice(CmdRequest request) { + + } + + @Override + public void deviceOpenCamera(CmdRequest request) { + + } + + @Override + public void openSetting(CmdRequest request) { + + } + + @Override + public void setWifiStatus(CmdRequest request) { + + } + + @Override + public void openBrowser(CmdRequest request) { + + } + + @Override + public void installApp(CmdRequest request) { + + } + + @Override + public void inputKeyDown(CmdRequest request) { + Map data = request.getData(); + ParamCheck keyCodeCheck = ParamCheck.build().name("key").type("string"); + Map params = null; + try { + params = checkParam(data, false, keyCodeCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + String key = (String) params.get("key"); + setKeyCodeStatus(key, true); //设置按键状态 + SessionUtils.sendSuccessData(session, request, null, "摁下键[" + key + "]处理成功"); + } + + @Override + public void inputKeyUp(CmdRequest request) { + Map data = request.getData(); + ParamCheck keyCodeCheck = ParamCheck.build().name("key").type("string"); + Map params = null; + try { + params = checkParam(data, false, keyCodeCheck); + } catch (Exception e) { + logger.error("参数校验失败!", e); + if (e instanceof ParamMistakeException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + return; + } + } + String key = (String) params.get("key"); + setKeyCodeStatus(key, false); + int code = HarmonyKeyBoardCodeEnum.getCode(key, !shiftDown); + if (code < 0) { + SessionUtils.sendFailureMessage(session, request, "键[" + key + "]不存在"); + return; + } + List codeList = new ArrayList<>(); + if (altDown) { + codeList.add(HarmonyKeyCode.KEYCODE_ALT_LEFT); + } + if ((capsDown && !shiftDown) || (!capsDown && shiftDown)) { + codeList.add(HarmonyKeyCode.KEYCODE_SHIFT_LEFT); + } + if (ctrlDown) { + codeList.add(HarmonyKeyCode.KEYCODE_CTRL_LEFT); + } + codeList.add(code); + logger.debug("输入code:{}", JSON.toJSONString(codeList)); + HarmonyDevice currentDevice = HarmonyDeviceManager.getInstance().getCurrentDevice(this.serial); + if (null != currentDevice) { + boolean success = harmonyDevice.keyEvent(codeList); + if (success) { + SessionUtils.sendSuccessData(session, request, null, "抬起键[" + key + "]处理成功"); + } else { + logger.warn("设备【{}】输入文本失败............................",this.serial); + } + } else { + SessionUtils.sendFailureMessage(session, request, "设备掉线,抬起键[" + key + "]失败"); + } + } + + @Override + public void getAppList(CmdRequest request) { + + } + + @Override + public void uninstallApp(CmdRequest request) { + + } + + @Override + public void sendShellToDevice(CmdRequest request) { + + } + + @Override + public void startLogcat(CmdRequest request) { + + } + + @Override + public void stopLogcat(CmdRequest request) { + + } + + @Override + public void startPerf(CmdRequest request) { + + } + + @Override + public void stopPerf(CmdRequest request) { + + } + + @Override + public void closeADBDebug(CmdRequest request) { + + } + + @Override + public void openADBDebug(CmdRequest request) { + + } + + @Override + public void pullFileFromDevice(CmdRequest request) { + + } + + @Override + public void pushFileToDevice(CmdRequest request) { + + } + + @Override + public void getFileList(CmdRequest request) { + + } + + @Override + public void downloadLogcat(CmdRequest request) { + + } + + @Override + public void startUiNode(CmdRequest request) { + if (null == nodeTreeThread || nodeTreeThread.isInterrupted() || !nodeTreeThread.isAlive()) { + try { + HarmonyProvider harmonyProvider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(this.serial); + if (null == harmonyProvider) { + SessionUtils.sendFailureMessage(session, request, "设备还未准备就绪,请稍后再试"); + return; + } + nodeTreeThread = new HarmonyGetNodeTreeThread(session,this.serial,request); + } catch (Exception e) { + if (e instanceof ExecuteException) { + SessionUtils.sendFailureMessage(session, request, e.getMessage()); + } else { + SessionUtils.sendFailureMessage(session, request, "获取设备节点失败"); + } + return; + } + nodeTreeThread.start(); + } else { + nodeTreeThread.startGetNodeTree(request); + } + } + + @Override + public void stopUiNode(CmdRequest request) { + if (null != nodeTreeThread) { + nodeTreeThread.stopGetNodeTree(request); + return; + } + SessionUtils.sendSuccessData(session, request, null,"该设备尚未开启获取节点树"); + } + + @Override + public void getNodeTreeOnce(CmdRequest request) { + + } + + @Override + public void clearAndCloseDeviceAgentConnection() { + + } + + @Override + public void reConnectDeviceAgent() { + + } + + @Override + public void initDeviceRequireParam() { + logger.info("获取手机【{}】的实际宽高............................",this.serial); + HarmonyProvider harmonyProvider = getHarmonyProvider(); + DisplaySize screenSize = harmonyProvider.getScreenSize(); + if (null == screenSize) { + throw new ParamMistakeException(String.format("获取手机【%s】实际宽高失败,请稍后再试!", this.serial)); + } + catchParam.setRealHeight(screenSize.height); + catchParam.setRealWidth(screenSize.width); + logger.info("手机【{}】的实际宽:{}高:{}............................", this.serial,catchParam.getRealWidth(), catchParam.getRealHeight()); + } + + private HarmonyProvider getHarmonyProvider(){ + HarmonyProvider provider = HarmonyDeviceManager.getInstance().getCurrentDeviceProvider(this.serial); + if (null == provider) { + throw new ExecuteException("当前设备已经掉线,请检查设备"); + } + return provider; + } + + private Point calculatePoint(Map data) { + Double x = Double.parseDouble(data.get("x") + ""); + Double y = Double.parseDouble(data.get("y") + ""); + Double realX = x.doubleValue() / catchParam.getWidth() * catchParam.getRealWidth(); + Double realY = y.doubleValue() / catchParam.getHeight() * catchParam.getRealHeight(); + return new Point(realX.floatValue(), realY.floatValue()); + } + + /** + * 设置摁下的是否是shift,ctrl,alt + * + * @param key + * @param down 摁下为true,抬起为false + */ + private void setKeyCodeStatus(String key, boolean down) { + if (key.equals(KeyBoardCodeEnum.KEYCODE_SHIFT_LEFT.getWebValue())) { + shiftDown = down; + } else if (key.equals(KeyBoardCodeEnum.KEYCODE_ALT_LEFT.getWebValue())) { + altDown = down; + } else if (key.equals(KeyBoardCodeEnum.KEYCODE_CTRL_LEFT.getWebValue())) { + ctrlDown = down; + } else if (capsUpLetters.contains(key)) { //包含大写字母 + if (shiftDown) { //如果是按了shift + logger.debug("没摁大写锁定"); + capsDown = false; + } else { + logger.debug("摁了大写锁定"); + capsDown = true; + } + } else if (capsDownLetters.contains(key)) { //小写字母 + if (shiftDown) { //如果按了shift + logger.debug("没摁大写锁定"); + capsDown = true; + } else { + logger.debug("摁了大写锁定"); + capsDown = false; + } + } else if (key.equals(KeyBoardCodeEnum.KEYCODE_CAPS_LOCK.getWebValue())) { + logger.debug("摁大写锁定。。。。。。。。。。"); + capsDown = !capsDown; + } + } +} diff --git a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/MessageHandler.java b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/MessageHandler.java index fca6231..2adc654 100644 --- a/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/MessageHandler.java +++ b/cctp-atu/atu-upper-computer/src/main/java/net/northking/cctp/upperComputer/webSocket/thread/MessageHandler.java @@ -101,4 +101,6 @@ public interface MessageHandler { void notifyDeviceOfflineToWeb(); void notifyDeviceOnlineToWeb(); + + void initDeviceRequireParam(); } diff --git a/cctp-production/cctp-mobile/src/main/java/net/northking/cctp/mobile/thread/ProcessMsgThread.java b/cctp-production/cctp-mobile/src/main/java/net/northking/cctp/mobile/thread/ProcessMsgThread.java index 58cabc2..55e935a 100644 --- a/cctp-production/cctp-mobile/src/main/java/net/northking/cctp/mobile/thread/ProcessMsgThread.java +++ b/cctp-production/cctp-mobile/src/main/java/net/northking/cctp/mobile/thread/ProcessMsgThread.java @@ -253,7 +253,15 @@ public class ProcessMsgThread extends Thread{ String serial = mobileDevice.getId(); String platform = mobileDevice.getPlatform(); if (StringUtils.hasText(ctrlIp) && StringUtils.hasText(ctrlPort) && StringUtils.hasText(serial) && StringUtils.hasText(platform)) { - String plat = "0".equals(platform) ? "android" : "ios"; + String plat = "android"; + if ("0".equals(platform)) { + plat = "android"; + }else if ("1".equals(platform)) { + plat = "ios"; + }else if ("2".equals(platform)) { + plat = "harmony"; + } + logger.info("手机【{}】的平台为:{}", serial, plat); urlInfo = String.format(TEMPLATE_UPPER_WS_URI, ctrlIp, ctrlPort, serial, plat); } } diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/pom.xml b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/pom.xml new file mode 100644 index 0000000..cc31934 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/pom.xml @@ -0,0 +1,93 @@ + + + + cctp-test-element-library + net.northking.cctp + 3.0-SNAPSHOT + + 4.0.0 + + cctp-test-element-mobile-hnos + + + + net.northking.cctp + atu-mobile-driver + 1.0.0-RELEASE + + + org.slf4j + slf4j-api + + + + + cn.hutool + hutool-core + 5.5.8 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + org.springframework + spring-web + 5.2.15.RELEASE + + + + NK.MobileHnos-1.0.14 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + UTF-8 + UTF-8 + UTF-8 + + + + xml-doclet + + prepare-package + + javadoc + + + com.github.markusbernhardt.xmldoclet.XmlDoclet + + + -d ${project.build.directory}/classes + + + -filename net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.javadoc + + + false + ${java.home}/../bin + + net/northking/cctp/element/mobile/hnos/keywords/*.java + net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.java + + + com.github.markusbernhardt + xml-doclet + ${xml.doclet.version} + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + \ No newline at end of file diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.java new file mode 100644 index 0000000..074493e --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/MobileHnosLibrary.java @@ -0,0 +1,52 @@ +package net.northking.cctp.element.mobile.hnos; + +import net.northking.cctp.element.core.AbstractLibrary; +import net.northking.cctp.element.core.annotation.Library; +import net.northking.cctp.element.core.type.LibraryConstant; +import net.northking.cctp.element.util.Javadoc2Libdoc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

该组件用于鸿蒙Next手机的操控

+ */ +@Library(scanBasePackage = "net.northking.cctp.element.mobile.hnos",infoClass = MobileHnosLibrary.class) +public class MobileHnosLibrary extends AbstractLibrary { + + private static final Logger logger = LoggerFactory.getLogger(MobileHnosLibrary.class); + private static final Javadoc2Libdoc JAVA_DOC_2_LIB_DOC = new Javadoc2Libdoc(MobileHnosLibrary.class); + private static final String NAME = "NK.MobileHnos"; + private static final String CH_NAME = "移动端鸿蒙Next组件库"; + private static final String VERSION = "1.0.14"; + private static final Integer TYPE = LibraryConstant.LIB_TYPE_HarmonyNext; + + @Override + public Javadoc2Libdoc getLibDoc() { + return JAVA_DOC_2_LIB_DOC; + } + + @Override + public void destroy() { + logger.info("destroy done."); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getChName() { + return CH_NAME; + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public Integer getType() { + return TYPE; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/CommandId.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/CommandId.java new file mode 100644 index 0000000..57c44fa --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/CommandId.java @@ -0,0 +1,34 @@ +package net.northking.cctp.element.mobile.hnos.constants; + +public enum CommandId { + + NODE_CLICK("节点点击",5), + POINT_CLICK("坐标点击", 21), + POINT_SWIPE("滑动", 24), + INPUT_TEXT("输入文本",18); + + private String name; + + private int code; + + CommandId(String name,int code){ + this.code = code; + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UpperParamKey.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UpperParamKey.java new file mode 100644 index 0000000..aa1ebfa --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UpperParamKey.java @@ -0,0 +1,145 @@ +package net.northking.cctp.element.mobile.hnos.constants; + +public interface UpperParamKey { + + /** + * 指令 + */ + String COMMAND_ID = "command_id"; + + /** + * 滑动相关 + */ + String IS_SWIPE = "is_swipe"; + + String SWIPE_DIRECTION = "swipe_direction"; + + String SWIPE_COUNT = "swipe_count"; + + String SWIPE_SIZE = "swipe_size"; + + String SEARCH_SWIPE = "SEARCH_SWIPE"; + + String TIMES = "times"; // 只查找一次 + + /** + * 基本参数 + */ + String WAIT_STATUS = "wait_status"; + + String WAIT_TIME_OUT = "wait_time_out"; + + + /** + * 节点相关 + */ + String NODE_TREE = "node_tree"; + + String POINT = "point"; + + /** + * 屏幕相关 + */ + String X = "x"; + String Y = "y"; + String SCREEN_WIDTH = "screen_width"; + String SCREEN_HEIGHT = "screen_height"; + String WIDTH = "width"; + String HEIGHT = "height"; + String OCR_TEXT = "ocr_text"; + String INDEX = "index"; + + + /** + * 图像相关 + */ + String IMG_URL = "img_url"; + + /** + * 图像相关,目标图分辨率 + */ + String RESOLUTION_S = "resolution_s"; + + /** + * 输入相关 + */ + String CLEAR = "clear"; + + String INPUT_TEXT = "input_text"; + + /** + * 设备信息 + */ + String DEVICE_INFO = "device_info"; + + /** + * 应用包名 + */ + String APP_PACKAGE = "app_package"; + + /** + * 应用操作类型 + */ + String TYPE = "type"; + + /** + * 超时时间 + */ + String WAIT_TIMEOUT = "waitTimeout"; + + /** + * 识别类型 + */ + String USING_TYPE = "using_type"; + + /** + * 租户id + */ + String TENANT_ID = "tenant_id"; + + /** + * 节点类型 + */ + String NODE_TYPE = "node_type"; + + /** + * 验证码类型 + */ + String SWIPE_POINT = "swipe_point"; + + /** + * 滑动解锁左上角点 + */ + String FIRST_POINT = "first_point"; + + /** + * 滑动解锁左上角右边第一个点 + */ + String SECOND_POINT = "second_point"; + + /** + * 滑动解锁左上角下边第一个点 + */ + String THIRD_POINT = "third_point"; + + /** + * 滑动解锁每一行每一列的点数 + */ + String POINT_NUM = "point_num"; + + /** + * 验证码类型 + */ + String CODE_TYPE = "code_type"; + + /** + * 是否需要模糊匹配,1:需要。0:不需要 + */ + String DIFFUSSION = "diffussion"; + + /** + * 方向 + */ + String DIRECTION = "direction"; + +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UsingType.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UsingType.java new file mode 100644 index 0000000..68b2641 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/constants/UsingType.java @@ -0,0 +1,20 @@ +package net.northking.cctp.element.mobile.hnos.constants; + +public interface UsingType { + + String SELECTOR = "selector"; //xpath方式 + String OCR = "ocr"; //ocr + String POINT = "point"; //坐标 + String IMAGE = "image"; //图像 + + interface Key{ + String SELECTOR_KEY = "selector"; + String TYPE = "type"; + + + + + + + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/ElementHandleParam.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/ElementHandleParam.java new file mode 100644 index 0000000..09cdb83 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/ElementHandleParam.java @@ -0,0 +1,412 @@ +package net.northking.cctp.element.mobile.hnos.entity; + +import net.northking.cctp.element.core.DeviceDriver; +import net.northking.cctp.element.core.IExecuteContext; +import net.northking.cctp.element.core.IStepTarget; + +import java.util.List; + +public class ElementHandleParam { + + private IExecuteContext context; + + private DeviceDriver deviceDriver; + + private List targets; + + private Integer waitTimeout; + + private Integer codeType; + + private boolean waitStatus; + + private String swipe; //0不滑动,left左滑,right右滑,top上滑,down下滑,该参数用于查找控件时选项 + + private Integer swipeCount; //滑动次数 + + private String swipeDirection; //0不滑动,left左滑,right右滑,top上滑,down下滑,该参数用于滑动组件 + + private Integer swipeSize; //滑动距离 + + private Integer xMove; //x方向偏移量 + + private Integer yMove; //y方向偏移量 + + private boolean clear; //是否清除文本 + + private String inputTextValue; //输入文本的值 + + private String text; //点击文本时文本、密码输入时输入的密码 + + private Integer index; //第几个 + + private String appPackage; //应用包名 + + private String type; //应用操作类型 + + private Integer x; //x坐标,用于画红点 + + private Integer y; //y坐标,用于画红点 + + private String references; //滑动元素,该参数用于滑动查找控件组件 + + private String direction; //方向 + + private List passingPoints; //滑动经过的点 + + private String firstPoint; + private String secondPoint; + private String thirdPoint; + private Integer pointNum; + private String ifMoney; + + public ElementHandleParam withPassingPoints(List passingPoints) { + this.setPassingPoints(passingPoints); + return this; + } + + public ElementHandleParam withPointNum(Integer pointNum) { + this.setPointNum(pointNum); + return this; + } + + public ElementHandleParam withIfMoney(String ifMoney) { + this.setIfMoney(ifMoney); + return this; + } + + public ElementHandleParam withPointData(String firstPoint, String secondPoint, String thirdPoint) { + this.setFirstPoint(firstPoint); + this.setSecondPoint(secondPoint); + this.setThirdPoint(thirdPoint); + return this; + } + + public ElementHandleParam withReferences(String references) { + this.setReferences(references); + return this; + } + + public ElementHandleParam withType(String type) { + this.setType(type); + return this; + } + + public ElementHandleParam withAppPackage(String appPackage) { + this.setAppPackage(appPackage); + return this; + } + + public ElementHandleParam withText(String text) { + this.setText(text); + return this; + } + + public ElementHandleParam withIndex(Integer index) { + this.setIndex(index); + return this; + } + + public String getAppPackage() { + return appPackage; + } + + public void setAppPackage(String appPackage) { + this.appPackage = appPackage; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(Integer index) { + this.index = index; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public static ElementHandleParam builder(DeviceDriver deviceDriver) { + return new ElementHandleParam().withDriver(deviceDriver); + } + + private ElementHandleParam withDriver(DeviceDriver deviceDriver) { + this.setDeviceDriver(deviceDriver); + return this; + } + + public ElementHandleParam withTargets(List targets) { + this.setTargets(targets); + return this; + } + + public ElementHandleParam waitTimeout(Integer waitTimeout) { + this.setWaitTimeout(waitTimeout); + return this; + } + + public ElementHandleParam codeType(Integer codeType) { + this.setCodeType(codeType); + return this; + } + + public ElementHandleParam waitStatus(Boolean waitStatus) { + this.setWaitStatus(waitStatus); + return this; + } + + public ElementHandleParam searchSwipeDirection(String searchSwipeDirection) { + this.setSwipe(searchSwipeDirection); + return this; + } + + public ElementHandleParam swipeDirection(String swipeDirection) { + this.setSwipeDirection(swipeDirection); + return this; + } + + public ElementHandleParam withDirection(String direction) { + this.setDirection(direction); + return this; + } + + public ElementHandleParam withSwipeCount(Integer swipeCount) { + this.setSwipeCount(swipeCount); + return this; + } + + public ElementHandleParam withSwipeSize(Integer swipeSize) { + this.setSwipeSize(swipeSize); + return this; + } + + public ElementHandleParam clear(boolean clear) { + this.setClear(clear); + return this; + } + + public ElementHandleParam withInputTextValue(String inputTextValue) { + this.setInputTextValue(inputTextValue); + return this; + } + + public ElementHandleParam withXMove(Integer xMove) { + this.setxMove(xMove); + return this; + } + + public ElementHandleParam withYMove(Integer yMove) { + this.setyMove(yMove); + return this; + } + + public ElementHandleParam useContext(IExecuteContext context) { + this.setContext(context); + return this; + } + + public IExecuteContext getContext() { + return context; + } + + public void setContext(IExecuteContext context) { + this.context = context; + } + + public DeviceDriver getDeviceDriver() { + return deviceDriver; + } + + public void setDeviceDriver(DeviceDriver deviceDriver) { + this.deviceDriver = deviceDriver; + } + + public List getTargets() { + return targets; + } + + public void setTargets(List targets) { + this.targets = targets; + } + + public Integer getWaitTimeout() { + return waitTimeout; + } + + public void setWaitTimeout(Integer waitTimeout) { + this.waitTimeout = waitTimeout; + } + + public boolean isWaitStatus() { + return waitStatus; + } + + public void setWaitStatus(boolean waitStatus) { + this.waitStatus = waitStatus; + } + + public String getSwipe() { + return swipe; + } + + public void setSwipe(String swipe) { + this.swipe = swipe; + } + + public Integer getSwipeCount() { + return swipeCount; + } + + public void setSwipeCount(Integer swipeCount) { + this.swipeCount = swipeCount; + } + + public Integer getxMove() { + return xMove; + } + + public void setxMove(Integer xMove) { + this.xMove = xMove; + } + + public Integer getyMove() { + return yMove; + } + + public void setyMove(Integer yMove) { + this.yMove = yMove; + } + + public Integer getSwipeSize() { + return swipeSize; + } + + public void setSwipeSize(Integer swipeSize) { + this.swipeSize = swipeSize; + } + + public String getSwipeDirection() { + return swipeDirection; + } + + public void setSwipeDirection(String swipeDirection) { + this.swipeDirection = swipeDirection; + } + + public boolean isClear() { + return clear; + } + + public void setClear(boolean clear) { + this.clear = clear; + } + + public String getInputTextValue() { + return inputTextValue; + } + + public void setInputTextValue(String inputTextValue) { + this.inputTextValue = inputTextValue; + } + + public void setPassingPoints(List passingPoints) { + this.passingPoints = passingPoints; + } + + public List getPassingPoints() { + return passingPoints; + } + + public Integer getCodeType() { + return codeType; + } + + public void setCodeType(Integer codeType) { + this.codeType = codeType; + } + + public Integer getX() { + return x; + } + + public void setX(Integer x) { + this.x = x; + } + + public Integer getY() { + return y; + } + + public void setY(Integer y) { + this.y = y; + } + + public String getReferences() { + return references; + } + + public void setReferences(String references) { + this.references = references; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public String getFirstPoint() { + return firstPoint; + } + + public void setFirstPoint(String firstPoint) { + this.firstPoint = firstPoint; + } + + public String getSecondPoint() { + return secondPoint; + } + + public void setSecondPoint(String secondPoint) { + this.secondPoint = secondPoint; + } + + public String getThirdPoint() { + return thirdPoint; + } + + public void setThirdPoint(String thirdPoint) { + this.thirdPoint = thirdPoint; + } + + public Integer getPointNum() { + return pointNum; + } + + public void setPointNum(Integer pointNum) { + this.pointNum = pointNum; + } + + public String getIfMoney() { + return ifMoney; + } + + public void setIfMoney(String ifMoney) { + this.ifMoney = ifMoney; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/PointMessage.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/PointMessage.java new file mode 100644 index 0000000..4d2ea77 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/PointMessage.java @@ -0,0 +1,106 @@ +package net.northking.cctp.element.mobile.hnos.entity; + + +public class PointMessage { + private int x; + private int y; + private boolean exchange; //是否转换屏幕比例 + private int xMove = 0; + private int yMove = 0; + private int scale = 1; //屏幕缩放比 + + private int xOriginal; + private int yOriginal; + + private Integer widthOriginal; + private Integer heightOriginal; + + public int getXOriginal() { + return xOriginal; + } + + public void setXOriginal(int xOriginal) { + this.xOriginal = xOriginal; + } + + public int getYOriginal() { + return yOriginal; + } + + public void setYOriginal(int yOriginal) { + this.yOriginal = yOriginal; + } + + public PointMessage(int x, int y, boolean exchange) { + this.x = x; + this.y = y; + this.exchange = exchange; + } + + public PointMessage() { + } + + public void setxMove(int xMove) { + this.xMove = xMove; + } + + public void setyMove(int yMove) { + this.yMove = yMove; + } + + public int getxMove() { + return xMove; + } + + public int getyMove() { + return yMove; + } + + public boolean isExchange() { + return exchange; + } + + public void setExchange(boolean exchange) { + this.exchange = exchange; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public int getScale() { + return scale; + } + + public void setScale(int scale) { + this.scale = scale; + } + + public Integer getWidthOriginal() { + return widthOriginal; + } + + public void setWidthOriginal(Integer widthOriginal) { + this.widthOriginal = widthOriginal; + } + + public Integer getHeightOriginal() { + return heightOriginal; + } + + public void setHeightOriginal(Integer heightOriginal) { + this.heightOriginal = heightOriginal; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/StepTarget.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/StepTarget.java new file mode 100644 index 0000000..60f8d39 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/entity/StepTarget.java @@ -0,0 +1,45 @@ +package net.northking.cctp.element.mobile.hnos.entity; + +import net.northking.cctp.element.core.IStepTarget; + +public class StepTarget implements IStepTarget { + + /** + * 元素查找方式 + */ + private String using; + /** + * 定位表达式 + */ + private String value; + /** + * 备选Or必选 + */ + private String strategy; + + @Override + public String getUsing() { + return using; + } + + public void setUsing(String using) { + this.using = using; + } + + @Override + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getStrategy() { + return strategy; + } + + public void setStrategy(String strategy) { + this.strategy = strategy; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/AppEnum.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/AppEnum.java new file mode 100644 index 0000000..e6bc48a --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/AppEnum.java @@ -0,0 +1,32 @@ +package net.northking.cctp.element.mobile.hnos.enums; + +import com.alibaba.fastjson.JSON; +import net.northking.cctp.element.core.type.EnumClass; + +import java.util.*; + +public enum AppEnum implements EnumClass { + RESTART("重启", "2"), + ACTIVE("启动", "1"), + CLOSE("关闭", "0"); + + private String key; + private String value; + + AppEnum(String key, String value){ + this.key = key; + this.value = value; + } + + @Override + public String getEnumData() { + List> result = new ArrayList<>(); + for (AppEnum nullEnum : EnumSet.allOf(AppEnum.class)) { + Map one = new HashMap(); + one.put("label",nullEnum.key); + one.put("value",nullEnum.value); + result.add(one); + } + return JSON.toJSONString(result); + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/SwipeDirection.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/SwipeDirection.java new file mode 100644 index 0000000..0ad0e70 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/SwipeDirection.java @@ -0,0 +1,34 @@ +package net.northking.cctp.element.mobile.hnos.enums; + +import com.alibaba.fastjson.JSON; +import net.northking.cctp.element.core.type.EnumClass; + +import java.util.*; + +public enum SwipeDirection implements EnumClass { + NO("不滑动","0"), + UP("向上","up"), + DOWN("向下","down"), + LEFT("向左","left"), + RIGHT("向右","right"); + + private String key; + private String value; + + SwipeDirection(String key, String value){ + this.key = key; + this.value = value; + } + + @Override + public String getEnumData() { + List> result = new ArrayList<>(); + for (SwipeDirection nullEnum : EnumSet.allOf(SwipeDirection.class)) { + Map one = new HashMap(); + one.put("label",nullEnum.key); + one.put("value",nullEnum.value); + result.add(one); + } + return JSON.toJSONString(result); + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/TextDirection.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/TextDirection.java new file mode 100644 index 0000000..3cd68f9 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/TextDirection.java @@ -0,0 +1,33 @@ +package net.northking.cctp.element.mobile.hnos.enums; + +import com.alibaba.fastjson.JSON; +import net.northking.cctp.element.core.type.EnumClass; + +import java.util.*; + +public enum TextDirection implements EnumClass { + UP("上","上"), + DOWN("下","下"), + LEFT("左","左"), + RIGHT("右","右"); + + private String key; + private String value; + + TextDirection(String key, String value){ + this.key = key; + this.value = value; + } + + @Override + public String getEnumData() { + List> result = new ArrayList<>(); + for (TextDirection nullEnum : EnumSet.allOf(TextDirection.class)) { + Map one = new HashMap(); + one.put("label",nullEnum.key); + one.put("value",nullEnum.value); + result.add(one); + } + return JSON.toJSONString(result); + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/WhetherOrNotEnum.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/WhetherOrNotEnum.java new file mode 100644 index 0000000..2741226 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/enums/WhetherOrNotEnum.java @@ -0,0 +1,35 @@ +package net.northking.cctp.element.mobile.hnos.enums; + +import com.alibaba.fastjson.JSON; +import net.northking.cctp.element.core.type.EnumClass; + +import java.util.*; + +/** + *

是否枚举类

+ */ +public enum WhetherOrNotEnum implements EnumClass { + + YES("是", "1"), + NO("否", "0"); + + private String key; + private String value; + + WhetherOrNotEnum(String key, String value){ + this.key = key; + this.value = value; + } + + @Override + public String getEnumData() { + List> result = new ArrayList<>(); + for (WhetherOrNotEnum nullEnum : EnumSet.allOf(WhetherOrNotEnum.class)) { + Map one = new HashMap(); + one.put("label",nullEnum.key); + one.put("value",nullEnum.value); + result.add(one); + } + return JSON.toJSONString(result); + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/keywords/HnosTools.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/keywords/HnosTools.java new file mode 100644 index 0000000..884ed9c --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/keywords/HnosTools.java @@ -0,0 +1,605 @@ +package net.northking.cctp.element.mobile.hnos.keywords; + +import net.northking.cctp.element.core.DeviceDriver; +import net.northking.cctp.element.core.IExecuteContext; +import net.northking.cctp.element.core.IStepTarget; +import net.northking.cctp.element.core.annotation.Argument; +import net.northking.cctp.element.core.annotation.Keyword; +import net.northking.cctp.element.core.annotation.Keywords; +import net.northking.cctp.element.core.annotation.Return; +import net.northking.cctp.element.core.exception.AssertException; +import net.northking.cctp.element.core.exception.ExecuteException; +import net.northking.cctp.element.core.type.DataType; +import net.northking.cctp.element.core.type.InputType; +import net.northking.cctp.element.core.type.ParamScope; +import net.northking.cctp.element.mobile.hnos.entity.ElementHandleParam; +import net.northking.cctp.element.mobile.hnos.entity.PointMessage; +import net.northking.cctp.element.mobile.hnos.enums.AppEnum; +import net.northking.cctp.element.mobile.hnos.enums.SwipeDirection; +import net.northking.cctp.element.mobile.hnos.enums.TextDirection; +import net.northking.cctp.element.mobile.hnos.enums.WhetherOrNotEnum; +import net.northking.cctp.element.mobile.hnos.utils.AutomationHandleUtil; +import net.northking.cctp.element.mobile.hnos.utils.CommonUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CancellationException; + +import static net.northking.cctp.element.mobile.hnos.utils.AutomationHandleUtil.getRedDotScreenShotWithPoint; + +/** + * 鸿蒙NExt的组件 + */ +@Keywords +public class HnosTools { + + private Logger logger = LoggerFactory.getLogger(HnosTools.class); + + /** + *

点击控件

+ * + * @param deviceDriver 设备驱动链接 + * @param targets 定位控件的信息 + * @param waitTimeout 默认超时时间 + * @param waitStatusStr 是否等待界面稳定 + * @param swipe 滑动方向 + * @param swipeCount 滑动次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "点击控件", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "点击结果", type = DataType.BOOLEAN) + public boolean clickElement(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, required = false, defaultValue = "0", inputType = InputType.select, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer swipeCount, +// @Argument(name = "xMove", scope = ParamScope.ARGS, comment = "水平偏移量", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer xMove, +// @Argument(name = "yMove", scope = ParamScope.ARGS, comment = "垂直偏移量", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer yMove, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait + ) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .waitStatus(waitStatus); + success = AutomationHandleUtil.clickElement(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

判断是否界面上存在某一个元素

+ * + * @param deviceDriver 设备驱动 + * @param targets 元素的信息 + * @param waitTimeout 默认超时时间 + * @param waitStatusStr 是否等待界面稳定 + * @param swipe 滑动方向 + * @param swipeCount 滑动次数 + * @param value 返回结果接受的值 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "是否存在元素", category = "0", attributes = "7") + @Return(name = "result", comment = "是否存在元素的结果", type = DataType.BOOLEAN) + public boolean ifElement(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, defaultValue = "0", required = false, defaultDisplay = false) Integer swipeCount, + @Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait) { + Boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .waitStatus(waitStatus); + success = AutomationHandleUtil.ifElement(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + success = false; + } + CommonUtils.setParamValue(context, value, success.toString()); + return success; + } + + /** + *

在控件中输入文本

+ * + * @param text 输入的文本 + * @param cleanStr 是否清空文本框0-否,1-是 + * @param deviceDriver 设备连接驱动 + * @param targets 定位控件参数 + * @param waitTimeout 超时时间 + * @param waitStatusStr 是否等待屏幕稳定 + * @param swipe 是否滑屏查找控件0-否,up-上滑,down-下滑,left-左滑,right-右滑 + * @param swipeCount 滑屏次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "控件输入文本", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "输入的结果", type = DataType.BOOLEAN) + public boolean inputText(IExecuteContext context, + @Argument(name = "text", scope = ParamScope.VARIABLES, comment = "输入的文本", type = DataType.STRING) String text, + @Argument(name = "clean", scope = ParamScope.ARGS, comment = "是否清空输入框", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String cleanStr, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer swipeCount, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait + ) { + Boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + boolean clear = "1".equals(cleanStr); //是否清除文本框 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .clear(clear) + .withInputTextValue(text) + .waitStatus(waitStatus); + success = AutomationHandleUtil.elementInput(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

获取控件的值

+ * + * @param deviceDriver 设备连接驱动 + * @param targets 定位控件参数 + * @param waitTimeout 超时时间 + * @param waitStatusStr 是否等待屏幕稳定 + * @param swipe 是否滑屏查找控件0-否,up-上滑,down-下滑,left-左滑,right-右滑 + * @param swipeCount 滑屏次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @param value 输出值 + * @return + */ + @Keyword(alias = "获取控件的值", category = "0", attributes = "7") + @Return(name = "result", comment = "获取控件值的结果", type = DataType.OBJECT) + public String getElementText(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer swipeCount, + @Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait) { + String result = ""; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .waitStatus(waitStatus); + result = AutomationHandleUtil.getElementValue(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + CommonUtils.setParamValue(context, value, result); + return result; + } + + /** + *

断言元素

+ * + * @param deviceDriver 设备连接驱动 + * @param targets 定位控件参数 + * @param waitTimeout 超时时间 + * @param waitStatusStr 是否等待屏幕稳定 + * @param swipe 是否滑屏查找控件0-否,up-上滑,down-下滑,left-左滑,right-右滑 + * @param swipeCount 滑屏次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "断言元素", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "断言结果", type = DataType.BOOLEAN) + public boolean assertElement(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, defaultValue = "0", required = false, defaultDisplay = false) Integer swipeCount, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "图片参数", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait + ) { + Boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .waitStatus(waitStatus); + success = AutomationHandleUtil.ifElement(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + } + if (success != null && success) { + return success; + } + throw new AssertException("断言失败,找不到目标元素"); + } + + /** + *

点击文本

+ * + * @param deviceDriver 设备连接驱动 + * @param waitTimeout 默认超时时间 + * @param text 点击的文本 + * @param index 选择第几个 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "点击文本", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "是否点击成功", type = DataType.BOOLEAN) + public Boolean clickText( + IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "text", comment = "点击文本", scope = ParamScope.ARGS, type = DataType.STRING) String text, + @Argument(name = "index", comment = "第几个", scope = ParamScope.ARGS, type = DataType.INTEGER, required = true, defaultValue = "1", defaultDisplay = false) Integer index, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait + + ) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .waitTimeout(waitTimeout) + .searchSwipeDirection("0") //不滑动 + .withSwipeCount(1) + .withText(text) + .withIndex(index); + success = AutomationHandleUtil.clickText(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

判断当前页面是否存在文本

+ * + * @param deviceDriver 设备连接驱动 + * @param waitTimeout 默认等待时长 + * @param text 判断的文本 + * @param value 接受返回结果 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return 返回判断的结果 + */ + @Keyword(alias = "是否存在文本", category = "0", attributes = "7") + @Return(name = "result", comment = "文本存在与不存在的结果", type = DataType.BOOLEAN) + public Boolean ifText( + IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "text", comment = "文本", scope = ParamScope.ARGS, type = DataType.STRING) String text, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, defaultValue = "0", required = false, defaultDisplay = false) Integer swipeCount, + @Argument(name = "value", scope = ParamScope.OUTPUT, comment = "组件返回值", type = DataType.STRING, defaultValue = "") String value, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait + ) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .withText(text); + PointMessage point = AutomationHandleUtil.ifText(param); + success = point != null; + getRedDotScreenShotWithPoint(param, point); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + success = false; + } + CommonUtils.setParamValue(context, value, String.valueOf(success)); + return success; + } + + /** + *

app处理

+ * + * @param deviceDriver 设备连接驱动 + * @param appPackage 启动包名 + * @param waitTimeout 等待超时时间 + * @param type 操作应用的类型0-关闭,1-启动 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "app处理", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "app处理", type = DataType.BOOLEAN) + public boolean handleApp(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "appPackage", scope = ParamScope.ARGS, comment = "应用包名", type = DataType.STRING) String appPackage, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "type", scope = ParamScope.ARGS, comment = "应用操作类型", type = DataType.ENUM, enumValues = AppEnum.class, inputType = InputType.select, defaultValue = "1") String type, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .withAppPackage(appPackage) + .withType(type) + .useContext(context) + .waitTimeout(waitTimeout); + success = AutomationHandleUtil.handleApp(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

点击home键

+ * + * @param deviceDriver 设备连接驱动 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "点击home键", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "点击home键", type = DataType.BOOLEAN) + public boolean pressKeyHome(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "0.0", defaultDisplay = false) Float sufExecuteWait) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .waitTimeout(30); + success = AutomationHandleUtil.pressKeyHome(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

标准滑动屏幕

+ * + * @param direction 滑动方向 + * @param deviceDriver 设备连接 + * @param size 滑动距离 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "标准滑动", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "滑动结果", type = DataType.BOOLEAN) + public boolean standardSwipe( + @Argument(name = "direction", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "up") String direction, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "size", scope = ParamScope.ARGS, comment = "滑动距离", type = DataType.INTEGER, required = false, notNull = false, defaultValue = "500") Integer size, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait + ) { + Boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .waitTimeout(waitTimeout) + .swipeDirection(direction) + .withSwipeSize(size); + success = AutomationHandleUtil.standardSwipe(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

控件滑动,以控件的中心坐标点为起点滑动

+ * + * @param direction 滑动方向 + * @param deviceDriver 设备驱动 + * @param size 滑动距离 + * @param targets 控件信息 + * @param waitTimeout 默认超时时间 + * @param waitStatusStr 是否等待界面稳定 + * @param swipe 滑动方向 + * @param swipeCount 滑动次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "控件滑动", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "滑动的结果", type = DataType.BOOLEAN) + public boolean elementSwipe(IExecuteContext context, + @Argument(name = "direction", scope = ParamScope.ARGS, comment = "控件滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, defaultDisplay = false, inputType = InputType.select, defaultValue = "up") String direction, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "size", scope = ParamScope.ARGS, comment = "滑动距离", type = DataType.INTEGER, required = false, notNull = false, defaultValue = "500") Integer size, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "查找控件滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, defaultValue = "0", required = false, defaultDisplay = false) Integer swipeCount, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait) { + Boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .swipeDirection(direction) + .withSwipeSize(size) + .waitStatus(waitStatus); + success = AutomationHandleUtil.elementSwipe(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + /** + *

长按控件

+ * + * @param deviceDriver 设备连接驱动 + * @param targets 定位控件参数 + * @param waitTimeout 超时时间 + * @param waitStatusStr 是否等待屏幕稳定 + * @param swipe 是否滑屏查找控件0-否,up-上滑,down-下滑,left-左滑,right-右滑 + * @param swipeCount 滑屏次数 + * @param preExecuteWait 执行前等待 + * @param sufExecuteWait 执行后等待 + * @return + */ + @Keyword(alias = "长按控件", category = "0", attributes = "7", commonlyUse = true) + @Return(name = "result", comment = "长按结果", type = DataType.BOOLEAN) + public boolean longPress(IExecuteContext context, + @Argument(name = "__deviceDriver", scope = ParamScope.CONTEXT, comment = "设备连接", type = DataType.OBJECT) DeviceDriver deviceDriver, + @Argument(name = "targets", scope = ParamScope.STEP, comment = "控件位置", type = DataType.LIST, defaultDisplay = false) List targets, + @Argument(name = "timeout", scope = ParamScope.ARGS, comment = "默认超时时间", type = DataType.INTEGER, required = false, defaultValue = "30", defaultDisplay = false) Integer waitTimeout, + @Argument(name = "waitStatus", scope = ParamScope.ARGS, comment = "等待界面稳定", type = DataType.ENUM, enumValues = WhetherOrNotEnum.class, inputType = InputType.select, defaultValue = "1", defaultDisplay = false) String waitStatusStr, + @Argument(name = "swipe", scope = ParamScope.ARGS, comment = "滑动方向", type = DataType.ENUM, enumValues = SwipeDirection.class, inputType = InputType.select, defaultValue = "0", required = false, defaultDisplay = false) String swipe, + @Argument(name = "swipeCount", scope = ParamScope.ARGS, comment = "滑动次数", type = DataType.INTEGER, defaultValue = "0", required = false, defaultDisplay = false) Integer swipeCount, + @Argument(name = "xMove", scope = ParamScope.ARGS, comment = "水平偏移量", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer xMove, + @Argument(name = "yMove", scope = ParamScope.ARGS, comment = "垂直偏移量", type = DataType.INTEGER, required = false, defaultValue = "0", defaultDisplay = false) Integer yMove, + @Argument(name = "preExecuteWait", scope = ParamScope.ARGS, comment = "执行前等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float preExecuteWait, + @Argument(name = "sufExecuteWait", scope = ParamScope.ARGS, comment = "执行后等待(单位:s)", type = DataType.INTEGER, required = false, defaultValue = "1.0", defaultDisplay = false) Float sufExecuteWait + ) { + boolean success = false; + try { + CommonUtils.handlePreExecuteWait(preExecuteWait); //执行前等待 + boolean waitStatus = "1".equals(waitStatusStr); //是否等待界面稳定 + ElementHandleParam param = ElementHandleParam.builder(deviceDriver) + .useContext(context) + .withTargets(targets) + .waitTimeout(waitTimeout) + .searchSwipeDirection(swipe) + .withSwipeCount(swipeCount) + .waitStatus(waitStatus) + .withXMove(xMove) + .withYMove(yMove); + success = AutomationHandleUtil.longPressElement(param); + CommonUtils.handleSufExecuteWait(sufExecuteWait); //执行后等待 + } catch (InterruptedException ie) { + logger.warn("用户取消查询元素"); + throw new ExecuteException("取消操作"); + } catch (Exception e) { + logger.error("出现了其他的问题。。。。", e); + throw new ExecuteException(e.getMessage()); + } + return success; + } + + +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/AutomationHandleUtil.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/AutomationHandleUtil.java new file mode 100644 index 0000000..30add8d --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/AutomationHandleUtil.java @@ -0,0 +1,1329 @@ +package net.northking.cctp.element.mobile.hnos.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import net.northking.cctp.element.core.DeviceDriver; +import net.northking.cctp.element.core.IExecuteContext; +import net.northking.cctp.element.core.IStepTarget; +import net.northking.cctp.element.core.exception.ExecuteException; +import net.northking.cctp.element.core.exception.ParamMistakeException; +import net.northking.cctp.element.mobile.hnos.constants.CommandId; +import net.northking.cctp.element.mobile.hnos.constants.UpperParamKey; +import net.northking.cctp.element.mobile.hnos.constants.UsingType; +import net.northking.cctp.element.mobile.hnos.entity.ElementHandleParam; +import net.northking.cctp.element.mobile.hnos.entity.PointMessage; +import net.northking.cctp.element.mobile.hnos.entity.StepTarget; +import net.northking.cctp.mobile.driver.constans.AutomationRequestCmd; +import net.northking.cctp.mobile.driver.driver.MobileDriver; +import net.northking.cctp.mobile.driver.entity.CmdAutomationRequest; +import net.northking.cctp.mobile.driver.entity.CmdAutomationResponse; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class AutomationHandleUtil { + + private final static Logger logger = LoggerFactory.getLogger(AutomationHandleUtil.class); + + /** + * 是否存在元素 + * + * @param param + * @return + */ + public static boolean ifElement(ElementHandleParam param) throws Exception{ + PointMessage point = findElementByTargets(param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (point == null) { + return false; + } else { + getRedDotScreenShotWithPoint(param, point); + return true; + } + } + + /** + * 点击元素 + * + * @param param + * @return + */ + public static boolean clickElement(ElementHandleParam param) throws Exception{ + getRedDotScreenShotWithTargets(param); + return clickElementByTargets(param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + } + + public static void getRedDotScreenShotWithTargets(ElementHandleParam param) throws Exception{ + Object isDebugObj = param.getContext().getContextVariable("__isDebug"); + boolean isDebug = false; + if (null == isDebugObj) { + logger.warn("isDebug获取为空!!"); + } else { + isDebug = (boolean) isDebugObj; + } + if (!isDebug) { + PointMessage point = findElementByTargets(param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (point != null) { + ElementHandleParam paramScreenShot = ElementHandleParam.builder(param.getDeviceDriver()) + .useContext(param.getContext()) + .waitTimeout(param.getWaitTimeout()); + paramScreenShot.setX(point.getX()); + paramScreenShot.setY(point.getY()); + try { + AutomationHandleUtil.screenShot(paramScreenShot); + } catch (Exception e) { + logger.error("红点截图失败:e", e); + } + } + } + } + + public static void getRedDotScreenShotWithPoint(ElementHandleParam param, PointMessage point) throws Exception{ + Object isDebugObj = param.getContext().getContextVariable("__isDebug"); + boolean isDebug = false; + if (null == isDebugObj) { + logger.warn("isDebug获取为空!!"); + } else { + isDebug = (boolean) isDebugObj; + } + if (!isDebug) { + if (point != null) { + ElementHandleParam paramScreenShot = ElementHandleParam.builder(param.getDeviceDriver()) + .useContext(param.getContext()) + .waitTimeout(param.getWaitTimeout()); + paramScreenShot.setX(point.getX()); + paramScreenShot.setY(point.getY()); + try { + AutomationHandleUtil.screenShot(paramScreenShot); + } catch (Exception e) { + logger.error("红点截图失败:e", e); + } + } + } + } + + private static boolean clickElementByTargets(DeviceDriver deviceDriver, List targets, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + boolean success = false; + for (IStepTarget target : targets) { + if (UsingType.SELECTOR.equals(target.getUsing())) { + success = nodeTreeHandleElement(deviceDriver, target, waitTimeout, swipe, swipeCount, CommandId.NODE_CLICK); + if (success) { + return success; + } + } else if (UsingType.OCR.equals(target.getUsing())) { + success = ocrHandleElement(deviceDriver,target, waitTimeout,swipe, swipeCount,CommandId.POINT_CLICK); + if (success) { + return success; + } + } else if (UsingType.POINT.equals(target.getUsing())) { + success = pointHandleElement(deviceDriver,target,waitTimeout,CommandId.POINT_CLICK); + if (success) { + return success; + } + } else if (UsingType.IMAGE.equals(target.getUsing())) { + success = imageHandleElement(deviceDriver, target, waitTimeout, swipe, swipeCount,CommandId.POINT_CLICK); + if (success) { + return success; + } + } else { + logger.warn("不支持的查找元素方式:{}", target.getUsing()); + } + } + throw new ExecuteException("未查找到控件"); + } + + private static boolean imageHandleElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount, CommandId command) throws Exception { + Boolean success = false; + Map paramMap = getImageParam(target.getValue(), swipe, swipeCount,waitTimeout); + if (null == paramMap) { + return false; + } + paramMap.put(UpperParamKey.COMMAND_ID, command.getCode()); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.HANDLE_ELEMENT_BY_IMAGE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据image操作的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + private static Map getImageParam(String param, String swipe, Integer swipeCount, Integer waitTimeout) { + JSONObject value = JSON.parseObject(param, JSONObject.class); + String imageUrl = value.getString("imgUri"); + String resolution_s = value.getString("resolution"); + if (StringUtils.isBlank(imageUrl)) { + logger.warn("图片地址为空.............."); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.IMG_URL, imageUrl); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.RESOLUTION_S, resolution_s); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, waitTimeout); + return paramMap; + } + + private static boolean pointHandleElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, CommandId command) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + Map paramMap = getPointParam(target.getValue()); + if (null == paramMap) { + return false; + } + paramMap.put(UpperParamKey.COMMAND_ID, command.getCode()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.HANDLE_ELEMENT_BY_POINT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据point点击的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + + } + + private static Map getPointParam(String param) { + JSONObject value = JSON.parseObject(param, JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + try { + x = value.getInteger("pointX"); + y = value.getInteger("pointY"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + } catch (Exception e) { + logger.error("坐标参数不对",e); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + return paramMap; + } + + private static boolean ocrHandleElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount, CommandId command) throws Exception{ + Boolean success = false; + Map paramMap = getOcrParam(target.getValue(), swipe, swipeCount); + if (null == paramMap) { + return false; + } + paramMap.put(UpperParamKey.COMMAND_ID, command.getCode()); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.HANDLE_ELEMENT_BY_OCR, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr点击的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + private static Map getOcrParam(String param, String swipe, Integer swipeCount) { + JSONObject value = JSON.parseObject(param, JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + Integer width = null; + Integer height = null; + try { + x = value.getInteger("x"); + y = value.getInteger("y"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + width = value.getInteger("width"); + height = value.getInteger("height"); + } catch (Exception e) { + logger.error("坐标参数不对",e); + return null; + } + String ocrText = value.getString("ocrText"); + if (StringUtils.isBlank(ocrText)) { + logger.warn("查找的文本为空................"); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.WIDTH, width); + paramMap.put(UpperParamKey.HEIGHT, height); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + paramMap.put(UpperParamKey.OCR_TEXT, ocrText); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.INDEX, value.get("index")); + return paramMap; + } + + private static Map getClickTextOcrParam(String swipe, Integer swipeCount, String text, Integer index) { + if (StringUtils.isBlank(text)) { + logger.warn("查找的文本为空................"); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.OCR_TEXT, text); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.INDEX, index); + return paramMap; + } + + /** + * 根据node节点操作元素 + * @param deviceDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @param command + * @return + */ + private static boolean nodeTreeHandleElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount, CommandId command) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + String xpath = object.getString(UsingType.Key.SELECTOR_KEY); + String type = object.getString(UsingType.Key.TYPE); + logger.info("拿到的xpath:{}, type:{}", xpath, type); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.COMMAND_ID, command.getCode()); + paramMap.put(UpperParamKey.NODE_TYPE, type); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, waitTimeout); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.HANDLE_ELEMENT_BY_NODE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据nodeTree操作元素的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + private static boolean clickElementByTargets(DeviceDriver deviceDriver, PointMessage point, Integer waitTimeout) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.POINT, point); + CmdAutomationRequest request = CmdAutomationRequest.builder(AutomationRequestCmd.CLICK_POINT, token, paramMap); + Object result = sendUpperMessageAndWaitReturn(deviceDriver, request, token, waitTimeout); + logger.debug("根据坐标点击的结果:{}",result); + if (null != request) { + success = JSONObject.parseObject(JSONObject.toJSONString(result), Boolean.class); + } else { + throw new ExecuteException("点击元素失败"); + } + return success; + } + + private static Object sendUpperMessageAndWaitReturn(DeviceDriver deviceDriver, CmdAutomationRequest request, String token, Integer waitTimeout) throws Exception { + MobileDriver mobileDriver = (MobileDriver) deviceDriver; + logger.warn("发送token:{}的消息..........................", token); + request.getData().put(UpperParamKey.WAIT_TIMEOUT, waitTimeout); + mobileDriver.send(JSON.toJSONString(request)); + mobileDriver.getSocketClient().semaphore = new Semaphore(0); + mobileDriver.getSocketClient().setToken(token); + if (mobileDriver.getSocketClient().semaphore.tryAcquire(waitTimeout + 3, TimeUnit.SECONDS)) { + CmdAutomationResponse response = mobileDriver.getSocketClient().getResponse(); + if (token.equals(response.getStepToken())) { + logger.warn("收到token:{}的消息..........................", token); + if (response.isSuccess()) { + Object data = response.getData(); + if (null == data) { + logger.debug("未获取到有效数据,原因:{}", response.getMessage()); + } + return data; + } else { + throw new ExecuteException(response.getMessage()); + } + } else { + logger.error("收到回来的消息token:{},发过去的token:{}。对不上,收到的内容:{}", response.getStepToken(), token, JSON.toJSONString(response)); + } + } else { + logger.warn("等待token:{},消息返回超时..........................", token); + throw new ExecuteException("处理超时"); + } + return null; + } + + /** + * 根据targets里面的内容查询对应的元素 + * @param deviceDriver + * @param targets + * @return + */ + private static PointMessage findElementByTargets(DeviceDriver deviceDriver, List targets, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + PointMessage point = null; + for (IStepTarget target : targets) { + switch (target.getUsing()) { + case UsingType.SELECTOR: + point = nodeTreeFindElement(deviceDriver, target, waitTimeout, swipe, swipeCount); + if (null != point) { + return point; + } + break; + case UsingType.OCR: + point = ocrFindElement(deviceDriver,target, waitTimeout,swipe, swipeCount); + if (null != point) { + return point; + } + break; + case UsingType.POINT: + point = pointFindElement(deviceDriver,target,waitTimeout); + if (null != point) { + return point; + } + break; + case UsingType.IMAGE: + point = imageFindElement(deviceDriver, target, waitTimeout, swipe, swipeCount); + if (null != point) { + return point; + } + break; + default: + logger.warn("不支持的查找元素方式:{}", target.getUsing()); + break; + } + } + return point; + } + + /** + * 根据targets里面的内容查询对应的元素 + * + * @param deviceDriver + * @param targets + * @return + */ + private static PointMessage findElementByTargetsForSwipe(DeviceDriver deviceDriver, List targets, Integer waitTimeout, String swipe, Integer swipeCount, Integer times) throws Exception{ + PointMessage point = null; + for (IStepTarget target : targets) { + switch (target.getUsing()) { + case UsingType.SELECTOR: + point = nodeTreeFindElementForSwipe(deviceDriver, target, waitTimeout, swipe, swipeCount, times); + break; + case UsingType.OCR: + point = ocrFindElement(deviceDriver, target, waitTimeout, swipe, swipeCount); + break; + case UsingType.POINT: + point = pointFindElement(deviceDriver, target, waitTimeout); + break; + case UsingType.IMAGE: + point = imageFindElementForSwipe(deviceDriver, target, waitTimeout, swipe, swipeCount, times); + break; + default: + logger.warn("不支持的查找元素方式:{}", target.getUsing()); + break; + } + } + return point; + } + + /** + * 以图找图查找元素的位置 + * @param deviceDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @return + */ + private static PointMessage imageFindElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + PointMessage point = null; + Map paramMap = getImageParam(target.getValue(), swipe, swipeCount, waitTimeout); + if (null == paramMap) { + return null; + } + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_IMAGE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据image查找的结果:{}",o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 以图找图查找元素的位置 + * + * @param deviceDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @return + */ + private static PointMessage imageFindElementForSwipe(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, + Integer swipeCount, Integer times) throws Exception{ + PointMessage point = null; + Map paramMap = getImageParam(target.getValue(), swipe, swipeCount, waitTimeout); + if (null == paramMap) { + return null; + } + paramMap.put(UpperParamKey.TIMES, times); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_IMAGE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据image查找的结果:{}", o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 根据ocr查找元素位置 + * @param deviceDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @return + */ + private static PointMessage ocrFindElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + PointMessage point = null; + Map paramMap = getOcrParam(target.getValue(), swipe, swipeCount); + if (null == paramMap) { + return null; + } + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_OCR, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr查找的结果:{}",o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 通过坐标的方式查找元素位置 + * @param deviceDriver + * @param target + * @param waitTimeout + * @return + */ + private static PointMessage pointFindElement(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout) throws Exception{ + PointMessage point = null; + Map paramMap = getPointParam(target.getValue()); + if (null == paramMap) { + return null; + } + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_POINT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据point查找的结果:{}",o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 通过节点的方式查找元素位置 + * @param mobileDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @return + */ + private static PointMessage nodeTreeFindElement(DeviceDriver mobileDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + PointMessage point = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + String xpath = object.getString(UsingType.Key.SELECTOR_KEY); + String type = object.getString(UsingType.Key.TYPE); + logger.info("拿到的xpath:{}, type:{}", xpath, type); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.NODE_TYPE, type); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, waitTimeout); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_NODE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(mobileDriver, builder, token, waitTimeout); + logger.debug("根据nodeTree查找的结果:{}", o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 通过节点的方式查找元素位置 + * + * @param mobileDriver + * @param target + * @param waitTimeout + * @param swipe + * @param swipeCount + * @return + */ + private static PointMessage nodeTreeFindElementForSwipe(DeviceDriver mobileDriver, IStepTarget target, Integer waitTimeout, + String swipe, Integer swipeCount, Integer times) throws Exception{ + PointMessage point = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + String xpath = object.getString(UsingType.Key.SELECTOR_KEY); + String type = object.getString(UsingType.Key.TYPE); + logger.info("拿到的xpath:{}, type:{}", xpath, type); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.NODE_TYPE, type); + paramMap.put(UpperParamKey.TIMES, times); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SEARCH_ELEMENT_BY_NODE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(mobileDriver, builder, token, waitTimeout); + logger.debug("根据nodeTree查找的结果:{}",o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + /** + * 标准滑动 + * @param param + * @return + */ + public static Boolean standardSwipe(ElementHandleParam param) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.SWIPE_DIRECTION, param.getSwipeDirection()); + paramMap.put(UpperParamKey.SWIPE_SIZE, param.getSwipeSize()); + paramMap.put(UpperParamKey.COMMAND_ID, CommandId.POINT_SWIPE.getCode()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SWIPE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("标准滑动的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + }else { + throw new ExecuteException("滑动超时"); + } + if (!success) { + throw new ExecuteException("滑动失败"); + } + return success; + } + + /** + * 控件滑动 + * @param param + * @return + */ + public static Boolean elementSwipe(ElementHandleParam param) throws Exception{ + PointMessage point = findElementByTargets(param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (point == null) { + logger.error("查找不到控件"); + throw new ExecuteException("查找不到控件"); + } + Boolean success = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + Integer x = point.getX(); + Integer y = point.getY(); + getRedDotScreenShotWithPoint(param, new PointMessage(point.getX(), point.getY(), false)); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SWIPE_DIRECTION, param.getSwipeDirection()); + paramMap.put(UpperParamKey.SWIPE_SIZE, param.getSwipeSize()); + paramMap.put(UpperParamKey.SEARCH_SWIPE, param.getSwipe()); + paramMap.put(UpperParamKey.SWIPE_COUNT, param.getSwipeCount()); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, param.getWaitTimeout()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.ELEMENT_SWIPE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("控件滑动的结果:{}", o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } else { + throw new ExecuteException("滑动超时"); + } + if (!success) { + throw new ExecuteException("滑动失败"); + } + return success; + + } + + public static Boolean elementInput(ElementHandleParam param) throws Exception{ + boolean success = false; + String xpath = null; + for (IStepTarget target : param.getTargets()) { + if (UsingType.SELECTOR.equals(target.getUsing())) { + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + xpath = object.getString(UsingType.Key.SELECTOR_KEY); + logger.info("拿到的nodeTree:{}", xpath); + break; + } + } + PointMessage point = findElementByTargets(param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (null == point) { + throw new ExecuteException("未找到输入的控件"); + } + getRedDotScreenShotWithPoint(param, point); + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.CLEAR, param.isClear()); + paramMap.put(UpperParamKey.INPUT_TEXT, param.getInputTextValue()); + paramMap.put(UpperParamKey.COMMAND_ID, CommandId.INPUT_TEXT.getCode()); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.X, point.getX()); + paramMap.put(UpperParamKey.Y, point.getY()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.INPUT_TEXT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("控件输入的结果:{}", o); + if (null != o) { + success = (boolean) o; + } else { + throw new ExecuteException("输入超时"); + } + if (!success) { + throw new ExecuteException("输入失败"); + } + return success; + } + + public static String getElementValue(ElementHandleParam param) throws Exception{ + String result = ""; + getRedDotScreenShotWithTargets(param); + List targets = param.getTargets(); + for (IStepTarget target : targets) { + for (IStepTarget item : targets) { + if (UsingType.SELECTOR.equalsIgnoreCase(item.getUsing())) { //xpath的方式 + result = nodeTreeGetElementValue(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (StringUtils.isNotBlank(result)) { + return result; + } + } else if (UsingType.OCR.equalsIgnoreCase(item.getUsing())) { //ocr方式 + result = ocrGetElementValue(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (StringUtils.isNotBlank(result)) { + return result; + } + } else { + logger.warn("不支持的获取元素值方式:{}", target.getUsing()); + } + } + } + if (StringUtils.isBlank(result)) { + throw new ExecuteException("无法获取当前元素的文本"); + } + return result; + } + + private static String nodeTreeGetElementValue(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + String result = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + String xpath = object.getString(UsingType.Key.SELECTOR_KEY); + String type = object.getString(UsingType.Key.TYPE); + logger.info("拿到的xpath:{}, type:{}", xpath, type); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.NODE_TYPE, type); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, waitTimeout); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_ELEMENT_VALUE_BY_NODE, token, paramMap); + result = (String) sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据nodeTree查找的结果:{}",result); + return result; + } + + private static String ocrGetElementValue(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + String result = null; + JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + Integer width = null; + Integer height = null; + try { + x = value.getInteger("x"); + y = value.getInteger("y"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + width = value.getInteger("width"); + height = value.getInteger("height"); + } catch (Exception e) { + logger.error("坐标参数不对", e); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.WIDTH, width); + paramMap.put(UpperParamKey.HEIGHT, height); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_ELEMENT_VALUE_BY_OCR, token, paramMap); + result = (String) sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr查找的结果:{}", result); + return result; + } + + //todo:重写 + public static boolean longPressElement(ElementHandleParam param) throws Exception{ + PointMessage point = findElementByTargets(param.getDeviceDriver(), param.getTargets(),param.getWaitTimeout(),param.getSwipe(),param.getSwipeCount()); + if (point == null) { + logger.error("查找不到控件"); + throw new ExecuteException("查找不到控件"); + } + /** + * 设置偏移量 + */ + point.setxMove(param.getxMove()); + point.setyMove(param.getyMove()); + getRedDotScreenShotWithPoint(param, point); + boolean success = longPressElementByPoint(param.getDeviceDriver(), point, param.getWaitTimeout()); + return success; + } + + private static boolean longPressElementByPoint(DeviceDriver deviceDriver, PointMessage point, Integer waitTimeout) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.X, point.getX() + point.getxMove()); + paramMap.put(UpperParamKey.Y, point.getY() + point.getyMove()); + CmdAutomationRequest request = CmdAutomationRequest.builder(AutomationRequestCmd.LONG_PRESS, token, paramMap); + Object result = sendUpperMessageAndWaitReturn(deviceDriver, request, token, waitTimeout); + logger.debug("根据坐标点击的结果:{}",result); + if (null != request) { + success = JSONObject.parseObject(JSONObject.toJSONString(result), Boolean.class); + } else { + throw new ExecuteException("点击元素失败"); + } + return success; + } + + + public static String getVerificationCode(ElementHandleParam param) throws Exception{ + String result = ""; + List targets = param.getTargets(); + getRedDotScreenShotWithTargets(param); + for (IStepTarget target : targets) { + for (IStepTarget item : targets) { + if (UsingType.SELECTOR.equalsIgnoreCase(item.getUsing())) { //xpath的方式 + result = nodeTreeGetVerificationCode(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (StringUtils.isNotBlank(result)) { + return result; + } + } else if (UsingType.OCR.equalsIgnoreCase(item.getUsing())) { //ocr方式 + result = result = ocrGetVerificationCode(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount(), param.getCodeType()); + if (StringUtils.isNotBlank(result)) { + return result; + } + } else { + logger.warn("不支持的获取元素值方式:{}", target.getUsing()); + } + } + } + if (StringUtils.isBlank(result)) { + throw new ExecuteException("无法获取验证码"); + } + return result; + } + + private static String ocrGetVerificationCode(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount, Integer codeType) throws Exception{ + String result = null; + JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + Integer width = null; + Integer height = null; + try { + x = value.getInteger("x"); + y = value.getInteger("y"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + width = value.getInteger("width"); + height = value.getInteger("height"); + } catch (Exception e) { + logger.error("坐标参数不对", e); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.USING_TYPE, target.getUsing()); + paramMap.put(UpperParamKey.WIDTH, width); + paramMap.put(UpperParamKey.HEIGHT, height); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + paramMap.put(UpperParamKey.CODE_TYPE, codeType); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_VERIFICATION_CODE_BY_OCR, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr查找的结果:{}", o); + if (null != o) { + result = JSONObject.parseObject(JSONObject.toJSONString(o), String.class); + } + return result; + } + + private static String nodeTreeGetVerificationCode(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + String result = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + JSONObject object = JSON.parseObject(target.getValue(), JSONObject.class); + String xpath = object.getString(UsingType.Key.SELECTOR_KEY); + String type = object.getString(UsingType.Key.TYPE); + logger.info("拿到的xpath:{}, type:{}", xpath, type); + paramMap.put(UpperParamKey.NODE_TREE, xpath); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.NODE_TYPE, type); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_VERIFICATION_CODE_BY_NODE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据nodeTree获取验证码的结果:{}",o); + if (null != o) { + result = JSONObject.parseObject(JSONObject.toJSONString(o), String.class); + } + return result; + } + + + public static boolean clickText(ElementHandleParam param) throws Exception{ + PointMessage point = ifText(param); + getRedDotScreenShotWithPoint(param, point); + boolean success = clickByPoint(param.getDeviceDriver(), param.getWaitTimeout(), CommandId.POINT_CLICK, point); + return success; + } + + private static boolean clickByPoint(DeviceDriver deviceDriver, Integer waitTimeout, CommandId pointClick, PointMessage point) throws Exception { + Boolean result = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + logger.info("点击的坐标=======>x:{},y:{}", point.getX(), point.getY()); + paramMap.put(UpperParamKey.X, point.getX()); + paramMap.put(UpperParamKey.Y, point.getY()); + paramMap.put(UpperParamKey.WAIT_TIME_OUT, waitTimeout); + paramMap.put(UpperParamKey.COMMAND_ID, pointClick.getCode()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.CLICK_POINT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("点击坐标的结果:{}",o); + if (null != o) { + result = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return result; + } + + + private static boolean clickTextByOcr(DeviceDriver deviceDriver, Integer waitTimeout, String swipe, Integer swipeCount, CommandId command, + String text, Integer index, IExecuteContext context) throws Exception{ + Boolean success = false; + if (text.length() > 20) { + throw new ParamMistakeException("点击的文本过长"); + } + String value = CommonUtils.handleVariable(context, text); + Map paramMap = getClickTextOcrParam(swipe,swipeCount,value,index); + if (null == paramMap) { + return false; + } + paramMap.put(UpperParamKey.COMMAND_ID, command.getCode()); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.CLICK_TEXT_BY_OCR, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr点击的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + + public static PointMessage ifText(ElementHandleParam param) throws Exception{ + PointMessage point = null; + Map paramMap = getClickTextOcrParam(param.getSwipe(),param.getSwipeCount(),param.getText(),param.getIndex()); + if (null == paramMap) { + return null; + } + paramMap.put(UpperParamKey.DIFFUSSION, 0); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.IF_TEXT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("是否存在文本的结果:{}",o); + if (null != o) { + point = JSONObject.parseObject(JSONObject.toJSONString(o), PointMessage.class); + } + return point; + } + + public static boolean handleApp(ElementHandleParam param) throws Exception{ + Boolean success = false; + Map paramMap = getHandleAppParam(param.getAppPackage(), param.getType()); + if (null == paramMap) { + return false; + } + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.HANDLE_APP, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("应用处理的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + private static Map getHandleAppParam(String appPackage, String type) { + if (StringUtils.isBlank(appPackage)) { + logger.warn("启动的应用包名为空................"); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.APP_PACKAGE, appPackage); + paramMap.put(UpperParamKey.TYPE, type); + return paramMap; + } + + public static boolean pressKeyHome(ElementHandleParam param) throws Exception{ + Boolean success = false; + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.PRESS_HOME_KEY, token, new HashMap<>()); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("点击home键的结果:{}",o); + if (null != o) { + success = JSONObject.parseObject(JSONObject.toJSONString(o), Boolean.class); + } + return success; + } + + + public static boolean inputPasswordByOcr(ElementHandleParam param) throws Exception{ + boolean result = false; + List targets = param.getTargets(); + for (IStepTarget target : targets) { + for (IStepTarget item : targets) { + if (UsingType.OCR.equalsIgnoreCase(item.getUsing())) { //ocr方式 + result = inputPasswordByOcr(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), + param.getSwipeCount(), param.getText()); + return result; + } else { + logger.warn("不支持的密码输入方式:{}", target.getUsing()); + } + } + } + return result; + } + + private static boolean inputPasswordByOcr(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, + Integer swipeCount, String text) throws Exception{ + boolean success = false; + Map paramMap = getInputPasswordByOcrParam(target.getValue(), swipe, swipeCount, text); + if (null == paramMap) { + return false; + } + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.INPUT_PASSWORD_BY_OCR, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("密码输入(ocr)的结果:{}",o); + if (null != o) { + success = (boolean) o; + } + return success; + } + + private static Map getInputPasswordByOcrParam(String param, String swipe, Integer swipeCount, String text) throws Exception{ + JSONObject value = JSON.parseObject(param, JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + Integer width = null; + Integer height = null; + try { + x = value.getInteger("x"); + y = value.getInteger("y"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + width = value.getInteger("width"); + height = value.getInteger("height"); + } catch (Exception e) { + logger.error("坐标参数不对",e); + return null; + } + if (StringUtils.isBlank(text)) { + logger.warn("输入的文本为空................"); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.WIDTH, width); + paramMap.put(UpperParamKey.HEIGHT, height); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + paramMap.put(UpperParamKey.IS_SWIPE, swipe); + paramMap.put(UpperParamKey.SWIPE_COUNT, swipeCount); + paramMap.put(UpperParamKey.INPUT_TEXT, text); + return paramMap; + } + + public static String getElementMoneyText(ElementHandleParam param) throws Exception{ + String result = ""; + getRedDotScreenShotWithTargets(param); + List targets = param.getTargets(); + for (IStepTarget target : targets) { + if (UsingType.OCR.equalsIgnoreCase(target.getUsing())) { //ocr方式 + result = getElementMoneyText(param.getDeviceDriver(), target, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (StringUtils.isNotBlank(result)) { + return result; + } + } else { + logger.warn("不支持的获取元素值方式:{}", target.getUsing()); + } + } + return result; + } + + private static String getElementMoneyText(DeviceDriver deviceDriver, IStepTarget target, Integer waitTimeout, String swipe, Integer swipeCount) throws Exception{ + String result = null; + JSONObject value = JSON.parseObject(target.getValue(), JSONObject.class); + Integer x = null; + Integer y = null; + Integer screenWidth = null; + Integer screenHeight = null; + Integer width = null; + Integer height = null; + try { + x = value.getInteger("x"); + y = value.getInteger("y"); + screenWidth = value.getInteger("screenWidth"); + screenHeight = value.getInteger("screenHeight"); + width = value.getInteger("width"); + height = value.getInteger("height"); + } catch (Exception e) { + logger.error("坐标参数不对", e); + return null; + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.USING_TYPE, target.getUsing()); + paramMap.put(UpperParamKey.WIDTH, width); + paramMap.put(UpperParamKey.HEIGHT, height); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SCREEN_WIDTH, screenWidth); + paramMap.put(UpperParamKey.SCREEN_HEIGHT, screenHeight); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + String token = UUID.randomUUID().toString(); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_ELEMENT_MONEY_TEXT, token, paramMap); + result = (String) sendUpperMessageAndWaitReturn(deviceDriver, builder, token, waitTimeout); + logger.debug("根据ocr查找的金额结果:{}", result); + return result; + } + + public static Boolean screenShot(ElementHandleParam param) throws Exception { + String screenShotUrl = ""; + String token = UUID.randomUUID().toString(); + String tenantId = (String) param.getContext().getContextVariable("__tenantId"); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.TENANT_ID, tenantId); + paramMap.put(UpperParamKey.X, param.getX()); + paramMap.put(UpperParamKey.Y, param.getY()); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + logger.debug("tenantId:{}",tenantId); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SCREEN_SHOT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("屏幕截图的结果:{}",o); + if (null != o) { + screenShotUrl = (String) o; + param.getContext().setVariable("mobileStepSnapshot",screenShotUrl); + return true; + } + return false; + } + + public static Boolean swipeAndFindTargetElement(ElementHandleParam param) throws Exception{ + Integer waitTimeout = param.getWaitTimeout(); + if (null == waitTimeout || waitTimeout <= 0) { + param.setWaitTimeout(30); + } + + String referenceStr = param.getReferences(); + logger.debug("执行滑动查找控件的操作,滑动控件:{}", referenceStr); + PointMessage point = null; + List references = JSONObject.parseArray(referenceStr, StepTarget.class); + List iStepTargets = new ArrayList<>(); + for (StepTarget reference : references) { + iStepTargets.add(reference); + } + if (!CollectionUtils.isEmpty(iStepTargets)) { + point = findElementByTargets(param.getDeviceDriver(), iStepTargets, param.getWaitTimeout(), param.getSwipe(), param.getSwipeCount()); + if (null == point) { + logger.warn("没有找到要滑动的控件位置...................."); + throw new ExecuteException("未找到需要滑动的元素"); + } + } + logger.debug("找到要滑动的控件位置:{}",JSON.toJSONString(point)); + PointMessage targetPoint = swipeToFindTargetElement(point, param.getContext(), param.getDeviceDriver(), param.getTargets(), param.getWaitTimeout(), + param.getSwipeDirection(), param); + getRedDotScreenShotWithPoint(param, point); + return targetPoint != null; + } + + private static PointMessage swipeToFindTargetElement(PointMessage swipePoint, IExecuteContext context, DeviceDriver driver, List targets, Integer waitTimeout, String direction, ElementHandleParam param) throws Exception{ + logger.debug("执行滑动查找控件的操作,目标控件:{}", JSON.toJSONString(targets)); + long startTime = System.currentTimeMillis(); + long endTime = System.currentTimeMillis(); + PointMessage targetPoint = null; + int num = 1; + do { + logger.debug("开始第{}次查找", num); + try { + targetPoint = findElementByTargetsForSwipe(param.getDeviceDriver(), param.getTargets(), 30, param.getSwipe(), param.getSwipeCount(), 1); + } catch (Exception ignore) { + logger.error(ignore.getMessage()); + } + logger.debug("第{}次查找的结果:{}", num, JSON.toJSONString(targetPoint)); + if (null == targetPoint) { + logger.debug("开始第{}次滑屏",num); + elementSwipeForSwipeToFindTargetElement(swipePoint,driver,direction,context); + } + num++; + endTime = System.currentTimeMillis(); + } while (targetPoint == null && endTime - startTime < waitTimeout * 1000); + logger.debug("在规定的时间内查找的结果:{}", JSON.toJSONString(targetPoint)); + return targetPoint; + } + + /** + * 控件滑动 + * @param swipePoint + * @param driver + * @param direction + * @param context + * @return + * @throws Exception + */ + private static Boolean elementSwipeForSwipeToFindTargetElement(PointMessage swipePoint, DeviceDriver driver, String direction, IExecuteContext context) throws Exception{ + Boolean success = null; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + if (swipePoint != null) { + Integer x = swipePoint.getXOriginal(); + Integer y = swipePoint.getYOriginal(); + paramMap.put(UpperParamKey.X, x); + paramMap.put(UpperParamKey.Y, y); + paramMap.put(UpperParamKey.SWIPE_DIRECTION, direction); + paramMap.put(UpperParamKey.SWIPE_SIZE, 500); + paramMap.put(UpperParamKey.SEARCH_SWIPE, 0); + paramMap.put(UpperParamKey.SWIPE_COUNT, 1); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.ELEMENT_SWIPE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(driver, builder, token, 30); + logger.debug("控件滑动的结果:{}", o); + } else { + paramMap.put(UpperParamKey.SWIPE_DIRECTION, direction); + paramMap.put(UpperParamKey.SWIPE_SIZE, 500); + paramMap.put(UpperParamKey.COMMAND_ID, CommandId.POINT_SWIPE.getCode()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.SWIPE_TO_FIND_TARGET_ELEMENT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(driver, builder, token, 30); + logger.debug("标准滑动的结果:{}", o); + } + return success; + } + + public static String getTextDirectionText(ElementHandleParam param) throws Exception{ + String result = ""; + String token = UUID.randomUUID().toString(); + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.DIRECTION, param.getDirection()); + paramMap.put(UpperParamKey.INPUT_TEXT, param.getText()); + paramMap.put(UpperParamKey.INDEX, param.getIndex()); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.GET_TEXT_DIRECTION_TEXT, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("获取指定文字指定方向的内容:{}", o); + if (null != o) { + result = (String) o; + } + return result; + } + + public static Boolean pressAndSwipe(ElementHandleParam param) throws Exception{ + boolean success = false; + String token = UUID.randomUUID().toString(); + String firstPointStr = param.getFirstPoint(); + String firstPoint = null; + List firstPointList = JSONObject.parseArray(firstPointStr, JSONObject.class); + for (JSONObject jsonObject : firstPointList) { + if (UsingType.SELECTOR.equals(jsonObject.getString("using"))) { + firstPoint = jsonObject.getString("value"); + break; + } + } + logger.debug("第一个点的定位xpath:{}", firstPoint); + if (StringUtils.isBlank(firstPoint)) { + throw new ExecuteException("左上角的点没有定位条件"); + } + String secondPointStr = param.getSecondPoint(); + String secondPoint = null; + List secondPointList = JSONObject.parseArray(secondPointStr, JSONObject.class); + for (JSONObject jsonObject : secondPointList) { + if (UsingType.SELECTOR.equals(jsonObject.getString("using"))) { + secondPoint = jsonObject.getString("value"); + break; + } + } + logger.debug("第二个点的定位xpath:{}", secondPoint); + if (StringUtils.isBlank(secondPoint)) { + throw new ExecuteException("左上角的右边第一个点没有定位条件"); + } + String thirdPointStr = param.getThirdPoint(); + String thirdPoint = null; + List thirdPointList = JSONObject.parseArray(thirdPointStr, JSONObject.class); + for (JSONObject jsonObject : thirdPointList) { + if (UsingType.SELECTOR.equals(jsonObject.getString("using"))) { + thirdPoint = jsonObject.getString("value"); + break; + } + } + logger.debug("第三个点的定位xpath:{}", thirdPoint); + if (StringUtils.isBlank(secondPoint)) { + throw new ExecuteException("左上角的下边第一个点没有定位条件"); + } + Map paramMap = new HashMap<>(); + paramMap.put(UpperParamKey.FIRST_POINT, firstPoint); + paramMap.put(UpperParamKey.SECOND_POINT, secondPoint); + paramMap.put(UpperParamKey.THIRD_POINT, thirdPoint); + paramMap.put(UpperParamKey.POINT_NUM, param.getPointNum()); + paramMap.put(UpperParamKey.SWIPE_POINT, param.getPassingPoints()); + paramMap.put(UpperParamKey.WAIT_TIMEOUT, param.getWaitTimeout()); + logger.info("参数:{}", JSON.toJSONString(paramMap)); + CmdAutomationRequest builder = CmdAutomationRequest.builder(AutomationRequestCmd.PRESS_AND_SWIPE, token, paramMap); + Object o = sendUpperMessageAndWaitReturn(param.getDeviceDriver(), builder, token, param.getWaitTimeout()); + logger.debug("滑动解锁的结果:{}",o); + if (null != o) { + success = (boolean) o; + } + if (!success) { + throw new ExecuteException("滑动解锁失败"); + } + return success; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/CommonUtils.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/CommonUtils.java new file mode 100644 index 0000000..e6a1d2b --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/CommonUtils.java @@ -0,0 +1,64 @@ +package net.northking.cctp.element.mobile.hnos.utils; + +import cn.hutool.core.util.ReUtil; +import net.northking.cctp.element.core.IExecuteContext; +import net.northking.cctp.element.core.exception.ExecuteException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +public class CommonUtils { + + private static Logger logger = LoggerFactory.getLogger(CommonUtils.class); + + public static void handleSufExecuteWait(Float sufExecuteWait) { + if (sufExecuteWait > 0) { + Float waitTime = sufExecuteWait * 1000; + try { + Thread.sleep(waitTime.longValue()); + } catch (InterruptedException e) { + throw new ExecuteException("取消操作"); + } + } + } + + public static void handlePreExecuteWait(Float preExecuteWait) { + if (preExecuteWait > 0) { + Float waitTime = preExecuteWait * 1000; + try { + Thread.sleep(waitTime.longValue()); + } catch (InterruptedException e) { + throw new ExecuteException("取消操作"); + } + } + } + + public static void setParamValue(IExecuteContext context, String variable, String value) { + try { + if (variable.contains("#{") && variable.contains("}")) { + String keyValue = ReUtil.get("(?<=\\#\\{)(.+?)(?=\\})", variable, 1); + // #{} 全局变量 + context.setProjectGlobalVariable(keyValue, value); + } + if (variable.contains("${") && variable.contains("}")) { + String keyValue = ReUtil.get("(?<=\\$\\{)(.+?)(?=\\})", variable, 1); + // #{} 全局变量 + context.setVariable(keyValue, value); + } + } catch (Exception e) { + throw new ExecuteException(String.format("参数【%s】设置异常!!", variable)); + } + } + + public static String handleVariable(IExecuteContext context, String text) { + if (StringUtils.hasText(text) && (RegexUtils.containScriptVar(text) || RegexUtils.containGlobalVar(text))) { //脚本变量 + String value = context.getVariable(text); + logger.debug("传入text:{},使用变量:{}去拿结果,拿到的结果:{}", text, text, value); + if (null == value) { + throw new ExecuteException(String.format("变量【%s】不存在!", value)); + } + return value; + } + return text; + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/HttpUtils.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/HttpUtils.java new file mode 100644 index 0000000..fecee4c --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/HttpUtils.java @@ -0,0 +1,95 @@ +package net.northking.cctp.element.mobile.hnos.utils; + +import net.northking.cctp.element.core.exception.ExecuteException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RequestCallback; +import org.springframework.web.client.ResponseExtractor; +import org.springframework.web.client.RestTemplate; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.Arrays; + +public class HttpUtils { + + private static final RestTemplate restTemplate; + + private static final RestTemplate downLoadRestTemplate; + + private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); + + static { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(30000); + factory.setReadTimeout(60000); + restTemplate = new RestTemplate(factory); + + SimpleClientHttpRequestFactory downloadFactory = new SimpleClientHttpRequestFactory(); + downloadFactory.setConnectTimeout(15000); + downloadFactory.setReadTimeout(10*60*1000); + downLoadRestTemplate = new RestTemplate(downloadFactory); + } + + public static T doPost(URI uri, HttpEntity httpEntity,Class responseType){ + ResponseEntity response = null; + try { + response = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, responseType); + } catch (Exception e) { + logger.error("请求出错,地址:{},原因:{}",uri.getPath(),e); + throw new ExecuteException(String.format("http请求【%s】出错", uri.getPath())); + } + return response.getBody(); + } + + public static T doPost(String uri, HttpEntity httpEntity,Class responseType){ + ResponseEntity response = null; + try { + response = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, responseType); + } catch (Exception e) { + logger.error("请求出错,地址:{},原因:{}",uri,e); + throw new ExecuteException(String.format("http请求【%s】出错", uri)); + } + return response.getBody(); + } + + public static T doGet(URI uri, Class responseType){ + ResponseEntity response = null; + try { + response = restTemplate.exchange(uri, HttpMethod.GET, null, responseType); + } catch (Exception e) { + logger.error("请求出错,地址:{},原因:{}",uri.getPath(),e); + throw new ExecuteException(String.format("http请求【%s】出错", uri.getPath())); + } + return response.getBody(); + } + + public static void downloadFileToLocal(String sourceUrl,String targetDir,String fileName){ + final String[] installAddr = {""}; + RequestCallback callback = request -> request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); + ResponseExtractor responseExtractor = clientHttpResponse -> { + File path = new File(targetDir); + if (!path.exists()) { + path.mkdirs(); + } + File file = new File(targetDir + "/" + fileName); + InputStream inputStream = clientHttpResponse.getBody(); + FileOutputStream outputStream = new FileOutputStream(file); + byte[] data = new byte[1024 * 1024]; + int length = 0; + while ((length = inputStream.read(data, 0, data.length)) > 0) { + outputStream.write(data, 0, length); + } + installAddr[0] = file.getAbsolutePath(); + return file; + }; + downLoadRestTemplate.execute(sourceUrl, HttpMethod.GET, callback, responseExtractor); + } +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/RegexUtils.java b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/RegexUtils.java new file mode 100644 index 0000000..7daacc7 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/java/net/northking/cctp/element/mobile/hnos/utils/RegexUtils.java @@ -0,0 +1,51 @@ +package net.northking.cctp.element.mobile.hnos.utils; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexUtils { + private final static String GLOBAL_VAR_REGEX = "#\\{([^}]*)\\}"; + private final static String SCRIPT_VAR_REGEX = "\\$\\{([^}]*)\\}"; + private final static Pattern GLOBAL_VAR_PATTERN = Pattern.compile(GLOBAL_VAR_REGEX); + private final static Pattern SCRIPT_VAR_PATTERN = Pattern.compile(SCRIPT_VAR_REGEX); + + private final static Logger log = LoggerFactory.getLogger(RegexUtils.class); + + private RegexUtils(){} + + public static boolean containGlobalVar(String str){ + return GLOBAL_VAR_PATTERN.matcher(str).find(); + } + public static Set extractGlobalVar(String str){ + Set ret = new HashSet<>(); + Matcher matcher = GLOBAL_VAR_PATTERN.matcher(str); + while(matcher.find()){ + String group = matcher.group(); + String var = group.substring(2,group.length()-1); + ret.add(var); + log.debug("find global var expr,expr:{},var:{}",group,var); + } + return ret; + } + public static boolean containScriptVar(String str){ + return SCRIPT_VAR_PATTERN.matcher(str).find(); + } + public static Set extractScriptVar(String str){ + Set ret = new HashSet<>(); + Matcher matcher = SCRIPT_VAR_PATTERN.matcher(str); + while(matcher.find()){ + String group = matcher.group(); + String var = group.substring(2,group.length()-1); + ret.add(var); + log.debug("find script var expr,expr:{},var:{}",group,var); + } + return ret; + } + +} diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/META-INF/cctp-class.factories b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/META-INF/cctp-class.factories new file mode 100644 index 0000000..9c5e389 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/META-INF/cctp-class.factories @@ -0,0 +1 @@ +net.northking.cctp.element.mobile.hnos.MobileHnosLibrary \ No newline at end of file diff --git a/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/changelog.md b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/changelog.md new file mode 100644 index 0000000..1c934a8 --- /dev/null +++ b/cctp-test-element/cctp-test-element-library/cctp-test-element-mobile-hnos/src/main/resources/changelog.md @@ -0,0 +1,5 @@ +### 版本:1.0.0 +更新于2024-10-28 + +1.库介绍 + 这是操作鸿蒙Next移动端的组件库。 \ No newline at end of file