/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <atomic>
#include <chrono>
#include <fstream>
#include <memory>
#include <iostream>
#include <thread>
#include <utility>
#include <condition_variable>
#include <sys/mman.h>
#ifdef HIDUMPER_ENABLED
#include <iservice_registry.h>
#include <system_ability_load_callback_stub.h>
#include "idump_broker.h"
#include "dump_broker_proxy.h"
#include "system_ability_definition.h"
#endif
#include "accessibility_event_info.h"
#include "accessibility_ui_test_ability.h"
#include "ability_manager_client.h"
#include "display_manager.h"
#include "screen_manager.h"
#include "input_manager.h"
#include "png.h"
#include "wm_common.h"
#include "element_node_iterator_impl.h"
#include "system_ui_controller.h"
#include "test_server_client.h"
#include "test_server_error_code.h"
#include "parameters.h"
#include "image_packer.h"

using namespace std;
using namespace chrono;

namespace OHOS::uitest {
    using namespace std;
    using namespace nlohmann;
    using namespace OHOS::MMI;
    using namespace OHOS::Accessibility;
    using namespace OHOS::Rosen;
    using namespace OHOS::Media;
    using namespace OHOS::HiviewDFX;
    using namespace OHOS;
    using OHOS::Accessibility::WindowUpdateType;
    using OHOS::Accessibility::EventType;

    class UiEventMonitor final : public AccessibleAbilityListener {
    public:
        virtual ~UiEventMonitor() override = default;

        void OnAbilityConnected() override;

        void OnAbilityDisconnected() override;

        void OnAccessibilityEvent(const AccessibilityEventInfo &eventInfo) override;

        void SetOnAbilityConnectCallback(function<void()> onConnectCb);

        void SetOnAbilityDisConnectCallback(function<void()> onDisConnectCb);

        bool OnKeyPressEvent(const shared_ptr<MMI::KeyEvent> &keyEvent) override
        {
            return false;
        }

        uint64_t GetLastEventMillis();

        bool WaitEventIdle(uint32_t idleThresholdMs, uint32_t timeoutMs);

        void WaitScrollCompelete();

        void RegisterUiEventListener(shared_ptr<UiEventListener> listerner);

    private:
        function<void()> onConnectCallback_ = nullptr;
        function<void()> onDisConnectCallback_ = nullptr;
        atomic<uint64_t> lastEventMillis_ = 0;
        atomic<uint64_t> lastScrollBeginEventMillis_ = 0;
        atomic<bool> scrollCompelete_ = true;
        vector<shared_ptr<UiEventListener>> listeners_;
        
        // Helper functions
        void NotifyListeners(const std::string& capturedEvent, const UiEventSourceInfo& uiEventSourceInfo,
            Widget* widget = nullptr);
    };

    struct EventSpec {
        std::string_view componentTyep;
        int32_t eventType;
        std::string_view event;
    };

    struct EventTypeSpec {
        int32_t eventType;
        std::string_view event;
    };

    const std::map<char, int32_t> SingleKeySymbalMap = {
        {' ', OHOS::MMI::KeyEvent::KEYCODE_SPACE},
        {'`', OHOS::MMI::KeyEvent::KEYCODE_GRAVE},
        {'[', OHOS::MMI::KeyEvent::KEYCODE_LEFT_BRACKET},
        {']', OHOS::MMI::KeyEvent::KEYCODE_RIGHT_BRACKET},
        {'\\', OHOS::MMI::KeyEvent::KEYCODE_BACKSLASH},
        {',', OHOS::MMI::KeyEvent::KEYCODE_COMMA},
        {';', OHOS::MMI::KeyEvent::KEYCODE_SEMICOLON},
        {'\'', OHOS::MMI::KeyEvent::KEYCODE_APOSTROPHE},
        {'/', OHOS::MMI::KeyEvent::KEYCODE_SLASH},
        {'*', OHOS::MMI::KeyEvent::KEYCODE_NUMPAD_MULTIPLY},
        {'-', OHOS::MMI::KeyEvent::KEYCODE_MINUS},
        {'.', OHOS::MMI::KeyEvent::KEYCODE_PERIOD},
        {'=', OHOS::MMI::KeyEvent::KEYCODE_EQUALS}
    };

    const std::map<char, int32_t> MultiKeySymbalMap = {
        {'~', OHOS::MMI::KeyEvent::KEYCODE_GRAVE},
        {'!', OHOS::MMI::KeyEvent::KEYCODE_1},
        {'@', OHOS::MMI::KeyEvent::KEYCODE_2},
        {'#', OHOS::MMI::KeyEvent::KEYCODE_3},
        {'$', OHOS::MMI::KeyEvent::KEYCODE_4},
        {'%', OHOS::MMI::KeyEvent::KEYCODE_5},
        {'^', OHOS::MMI::KeyEvent::KEYCODE_6},
        {'&', OHOS::MMI::KeyEvent::KEYCODE_7},
        {'(', OHOS::MMI::KeyEvent::KEYCODE_9},
        {')', OHOS::MMI::KeyEvent::KEYCODE_0},
        {'+', OHOS::MMI::KeyEvent::KEYCODE_EQUALS},
        {'_', OHOS::MMI::KeyEvent::KEYCODE_MINUS},
        {':', OHOS::MMI::KeyEvent::KEYCODE_SEMICOLON},
        {'"', OHOS::MMI::KeyEvent::KEYCODE_APOSTROPHE},
        {'<', OHOS::MMI::KeyEvent::KEYCODE_COMMA},
        {'>', OHOS::MMI::KeyEvent::KEYCODE_PERIOD},
        {'?', OHOS::MMI::KeyEvent::KEYCODE_SLASH},
        {'{', OHOS::MMI::KeyEvent::KEYCODE_LEFT_BRACKET},
        {'}', OHOS::MMI::KeyEvent::KEYCODE_RIGHT_BRACKET},
        {'|', OHOS::MMI::KeyEvent::KEYCODE_BACKSLASH}
    };

    static constexpr EventSpec WATCHED_EVENTS[] = {
        {"Toast", WindowsContentChangeTypes::CONTENT_CHANGE_TYPE_SUBTREE, "toastShow"},
        {"AlertDialog", WindowsContentChangeTypes::CONTENT_CHANGE_TYPE_SUBTREE, "dialogShow"}
    };

    static constexpr EventTypeSpec WATCHED_COMPONENT_EVENTS[] = {
        { EventType::TYPE_VIEW_CLICKED_EVENT, "componentEventOccur"},
        { EventType::TYPE_VIEW_LONG_CLICKED_EVENT, "componentEventOccur"},
        { EventType::TYPE_VIEW_SCROLLED_START, "componentEventOccur"},
        { EventType::TYPE_VIEW_SCROLLED_EVENT, "componentEventOccur"},
        { EventType::TYPE_VIEW_TEXT_SELECTION_UPDATE_EVENT, "componentEventOccur"},
    };

    static constexpr EventTypeSpec WATCHED_WINDOW_EVENTS[] = {
        { WindowUpdateType::WINDOW_UPDATE_ADDED, "windowChange"},
        { WindowUpdateType::WINDOW_UPDATE_REMOVED, "windowChange"},
        { WindowUpdateType::WINDOW_UPDATE_BOUNDS, "windowChange"}
    };

    static int32_t GetWindowChangeType(WindowUpdateType eventType)
    {
        switch (eventType) {
            case WindowUpdateType::WINDOW_UPDATE_ADDED:
                return WindowChangeType::WINDOW_ADDED;
            case WindowUpdateType::WINDOW_UPDATE_REMOVED:
                return WindowChangeType::WINDOW_REMOVED;
            case WindowUpdateType::WINDOW_UPDATE_BOUNDS:
                return WindowChangeType::WINDOW_BOUNDS_CHANGED;
            default:
                return WindowChangeType::WINDOW_UNDEFINED;
        }
    }

    static int32_t GetComPonentEventType(EventType eventType)
    {
        switch (eventType) {
            case EventType::TYPE_VIEW_CLICKED_EVENT:
                return ComponentEventType::COMPONENT_CLICKED;
            case EventType::TYPE_VIEW_LONG_CLICKED_EVENT:
                return ComponentEventType::COMPONENT_LONG_CLICKED;
            case EventType::TYPE_VIEW_SCROLLED_START:
                return ComponentEventType::COMPONENT_SCROLL_START;
            case EventType::TYPE_VIEW_SCROLLED_EVENT:
                return ComponentEventType::COMPONENT_SCROLL_END;
            case EventType::TYPE_VIEW_TEXT_SELECTION_UPDATE_EVENT:
                return ComponentEventType::COMPONENT_TEXT_CHANGED;
            default:
                return ComponentEventType::COMPONENT_UNDEFINED;
        }
    }
    static std::string GetWatchedEvent(const AccessibilityEventInfo &eventInfo)
    {
        auto eventCounts = sizeof(WATCHED_EVENTS) / sizeof(EventSpec);
        for (unsigned long index = 0; index < eventCounts; index++) {
            if (WATCHED_EVENTS[index].componentTyep == eventInfo.GetComponentType() &&
                WATCHED_EVENTS[index].eventType == eventInfo.GetWindowContentChangeTypes()) {
                LOG_W("Capture event: %{public}s", WATCHED_EVENTS[index].event.data());
                return string(WATCHED_EVENTS[index].event);
            }
        }

        static constexpr size_t componentEventCount = sizeof(WATCHED_COMPONENT_EVENTS) / sizeof(EventTypeSpec);
        auto componentType = static_cast<uint32_t>(eventInfo.GetEventType());
        for (size_t i = 0; i < componentEventCount; ++i) {
            if (static_cast<uint32_t>(WATCHED_COMPONENT_EVENTS[i].eventType) == componentType) {
                return std::string(WATCHED_COMPONENT_EVENTS[i].event);
            }
        }

        auto windowUpdateType = static_cast<uint32_t>(eventInfo.GetWindowChangeTypes());
        static constexpr size_t windowEventCount = sizeof(WATCHED_WINDOW_EVENTS) / sizeof(EventTypeSpec);
        for (size_t i = 0; i < windowEventCount; ++i) {
            if (static_cast<uint32_t>(WATCHED_WINDOW_EVENTS[i].eventType) == windowUpdateType) {
                return std::string(WATCHED_WINDOW_EVENTS[i].event);
            }
        }

        return "undefine";
    }

    // UiEventMonitor instance.
    static shared_ptr<UiEventMonitor> g_monitorInstance_ = make_shared<UiEventMonitor>();

    void UiEventMonitor::SetOnAbilityConnectCallback(function<void()> onConnectCb)
    {
        onConnectCallback_ = std::move(onConnectCb);
    }

    void UiEventMonitor::SetOnAbilityDisConnectCallback(function<void()> onDisConnectCb)
    {
        onDisConnectCallback_ = std::move(onDisConnectCb);
    }

    void UiEventMonitor::OnAbilityConnected()
    {
        if (onConnectCallback_ != nullptr) {
            onConnectCallback_();
        }
    }

    void UiEventMonitor::OnAbilityDisconnected()
    {
        if (onDisConnectCallback_ != nullptr) {
            onDisConnectCallback_();
        }
    }

    // the monitored events
    static const std::vector<uint32_t> EVENT_MASK = {
        EventType::TYPE_VIEW_TEXT_UPDATE_EVENT,
        EventType::TYPE_PAGE_STATE_UPDATE,
        EventType::TYPE_PAGE_CONTENT_UPDATE,
        EventType::TYPE_VIEW_SCROLLED_EVENT,
        EventType::TYPE_WINDOW_UPDATE,
        EventType::TYPE_PAGE_OPEN
    };

    void UiEventMonitor::RegisterUiEventListener(std::shared_ptr<UiEventListener> listerner)
    {
        listeners_.emplace_back(listerner);
    }

    static void GetElementBounds(const AccessibilityElementInfo &element, Rect &componentRect)
    {
        auto bounds = element.GetRectInScreen();
        componentRect.left_ = bounds.GetLeftTopXScreenPostion();
        componentRect.top_ = bounds.GetLeftTopYScreenPostion();
        componentRect.right_ = bounds.GetRightBottomXScreenPostion();
        componentRect.bottom_ = bounds.GetRightBottomYScreenPostion();
    }

    void UiEventMonitor::OnAccessibilityEvent(const AccessibilityEventInfo &eventInfo)
    {
        auto eventType = eventInfo.GetEventType();
        LOG_D("OnEvent:0x%{public}x", eventType);
        auto capturedEvent = GetWatchedEvent(eventInfo);
        if (eventType == Accessibility::EventType::TYPE_VIEW_SCROLLED_START) {
            LOG_I("Capture scroll begin");
            scrollCompelete_.store(false);
            lastScrollBeginEventMillis_.store(GetCurrentMillisecond());
        }
        if (eventType == Accessibility::EventType::TYPE_VIEW_SCROLLED_EVENT) {
            LOG_I("Capture scroll end");
            scrollCompelete_.store(true);
        }
        if (capturedEvent != "undefine") {
            LOG_D("testfwk Capture event: %{public}s", capturedEvent.c_str());
            auto bundleName = eventInfo.GetBundleName();
            auto contentList = eventInfo.GetContentList();
            auto text = !contentList.empty() ? contentList[0] : "";
            auto type = eventInfo.GetComponentType();
            int32_t windowChangeType = GetWindowChangeType(eventInfo.GetWindowChangeTypes());
            int32_t componentEventType = GetComPonentEventType(eventInfo.GetEventType());
            int32_t windowId = eventInfo.GetWindowId();
            Rect componentRect;
            AccessibilityWindowInfo windowInfo;
            if (AccessibilityUITestAbility::GetInstance()->GetWindow(windowId, windowInfo) == RET_OK) {
                bundleName = bundleName.empty() ? windowInfo.GetBundleName() : bundleName;
                componentRect.displayId_ = windowInfo.GetDisplayId();
            }
            LOG_D("testfwk OnAccessibilityEvent bundleName: %{public}s", bundleName.c_str());
            const auto& elemInfo = eventInfo.GetElementInfo();
            auto componentId = elemInfo.GetInspectorKey();
            GetElementBounds(elemInfo, componentRect);
            UiEventSourceInfo uiEventSourceInfo = {bundleName, text, type, windowChangeType, componentEventType,
                windowId, componentId, componentRect};
            std::unique_ptr<Widget> widget = nullptr;
            if (capturedEvent == "componentEventOccur") {
                widget = std::make_unique<Widget>("");
                ElementNodeIteratorImpl iterator;
                iterator.WrapperNodeAttrToVec(*widget, elemInfo);
                widget->SetDisplayId(windowInfo.GetDisplayId());
                widget->SetAttr(UiAttr::BUNDLENAME, bundleName);
            }
            NotifyListeners(capturedEvent, uiEventSourceInfo, widget.get());
        }
        if (std::find(EVENT_MASK.begin(), EVENT_MASK.end(), eventInfo.GetEventType()) != EVENT_MASK.end()) {
            lastEventMillis_.store(GetCurrentMillisecond());
        }
    }

    uint64_t UiEventMonitor::GetLastEventMillis()
    {
        if (lastEventMillis_.load() <= 0) {
            lastEventMillis_.store(GetCurrentMillisecond());
        }
        return lastEventMillis_.load();
    }

    void UiEventMonitor::WaitScrollCompelete()
    {
        if (scrollCompelete_.load()) {
            return;
        }
        auto currentMs = GetCurrentMillisecond();
        if (lastScrollBeginEventMillis_.load() <= 0) {
            lastScrollBeginEventMillis_.store(currentMs);
        }
        const auto idleThresholdMs = 10000;
        static constexpr auto sliceMs = 10;
        while (currentMs - lastScrollBeginEventMillis_.load() < idleThresholdMs) {
            if (scrollCompelete_.load()) {
                return;
            }
            this_thread::sleep_for(chrono::milliseconds(sliceMs));
            currentMs = GetCurrentMillisecond();
        }
        LOG_E("wait for scrollEnd event timeout.");
        scrollCompelete_.store(true);
        return;
    }

    bool UiEventMonitor::WaitEventIdle(uint32_t idleThresholdMs, uint32_t timeoutMs)
    {
        const auto currentMs = GetCurrentMillisecond();
        if (lastEventMillis_.load() <= 0) {
            lastEventMillis_.store(currentMs);
        }
        if (currentMs - lastEventMillis_.load() >= idleThresholdMs) {
            return true;
        }
        static constexpr auto sliceMs = 10;
        this_thread::sleep_for(chrono::milliseconds(sliceMs));
        if (timeoutMs <= sliceMs) {
            return false;
        }
        return WaitEventIdle(idleThresholdMs, timeoutMs - sliceMs);
    }

    void UiEventMonitor::NotifyListeners(const std::string& capturedEvent,
        const UiEventSourceInfo& uiEventSourceInfo, Widget* widget)
    {
        LOG_D("testfwk NotifyListeners capturedEvent: %{public}s", capturedEvent.c_str());
        for (auto &listener : listeners_) {
            listener->OnEvent(capturedEvent, uiEventSourceInfo, widget);
        }
    }

    SysUiController::SysUiController() : UiController() {}

    SysUiController::~SysUiController()
    {
        DisConnectFromSysAbility();
    }

    bool SysUiController::Initialize(ApiCallErr &error)
    {
#ifdef ARKXTEST_MULTI_USER_ENABLE
        int32_t userCounts = OHOS::testserver::TestServerClient::GetInstance().GetUserCounts();
        isSingleUser_ = userCounts == 1;
#else
        isSingleUser_ = true;
#endif
        return this->ConnectToSysAbility(error);
    }

    static Rect GetFoldArea()
    {
        auto foldCreaseRegion = DisplayManager::GetInstance().GetCurrentFoldCreaseRegion();
        auto areas = foldCreaseRegion->GetCreaseRects();
        if (areas.size() == 1) {
            auto foldArea = *areas.begin();
            LOG_D("foldArea, left: %{public}d, top: %{public}d, width: %{public}d, height: %{public}d",
                  foldArea.posX_, foldArea.posY_, foldArea.width_, foldArea.height_);
            return Rect(foldArea.posX_, foldArea.posX_ + foldArea.width_,
                        foldArea.posY_, foldArea.posY_ + foldArea.height_);
        } else {
            LOG_E("Invalid display info.");
            return Rect(0, 0, 0, 0);
        }
    }

    static Rect GetVisibleRect(Rect screenBounds, AccessibilityWindowInfo &node)
    {
        auto nodeBounds = node.GetRectInScreen();
        auto leftX = nodeBounds.GetLeftTopXScreenPostion();
        auto topY = nodeBounds.GetLeftTopYScreenPostion();
        auto rightX = nodeBounds.GetRightBottomXScreenPostion();
        auto bottomY = nodeBounds.GetRightBottomYScreenPostion();
        float scaleX = node.GetScaleX();
        float scaleY = node.GetScaleY();
        LOG_D("Get window %{public}d, leftX: %{public}d, topY: %{public}d, rightX: %{public}d, bottomY: %{public}d,"
              "scaleX: %{public}f, scaleY: %{public}f",
              node.GetWindowId(), leftX, topY, rightX, bottomY, scaleX, scaleY);
        scaleX = (fabs(scaleX) < 1e-6) ? 1.0 : scaleX;
        scaleY = (fabs(scaleY) < 1e-6) ? 1.0 : scaleY;
        rightX = leftX + (rightX - leftX) * scaleX;
        bottomY = topY + (bottomY - topY) * scaleY;
        if (node.GetDisplayId() == VIRTUAL_DISPLAY_ID) {
            auto foldArea = GetFoldArea();
            topY += foldArea.bottom_;
            bottomY += foldArea.bottom_;
        }
        Rect newBounds((leftX < screenBounds.left_) ? screenBounds.left_ : leftX,
                       (rightX > screenBounds.right_) ? screenBounds.right_ : rightX,
                       (topY < screenBounds.top_) ? screenBounds.top_ : topY,
                       (bottomY > screenBounds.bottom_) ? screenBounds.bottom_ : bottomY);
        return newBounds;
    }

    static WindowMode ConvertWindowMode(int32_t rawMode)
    {
        const auto origMode = static_cast<OHOS::Rosen::WindowMode>(rawMode);
        switch (origMode) {
            case OHOS::Rosen::WindowMode::WINDOW_MODE_FULLSCREEN:
                return WindowMode::FULLSCREEN;
            case OHOS::Rosen::WindowMode::WINDOW_MODE_SPLIT_PRIMARY:
                return WindowMode::SPLIT_PRIMARY;
            case OHOS::Rosen::WindowMode::WINDOW_MODE_SPLIT_SECONDARY:
                return WindowMode::SPLIT_SECONDARY;
            case OHOS::Rosen::WindowMode::WINDOW_MODE_FLOATING:
                return WindowMode::FLOATING;
            case OHOS::Rosen::WindowMode::WINDOW_MODE_PIP:
                return WindowMode::PIP;
            default:
                return WindowMode::UNKNOWN;
        }
    }

    static void InflateAbilityInfo(Window& info, const std::string& app,
        AccessibilityElementInfo& element, bool needAbilityInfo)
    {
        if (needAbilityInfo) {
            const auto foreAbility = AAFwk::AbilityManagerClient::GetInstance()->GetTopAbility();
            info.abilityName_ = (app == foreAbility.GetBundleName()) ? foreAbility.GetAbilityName() : "";
            info.pagePath_ = (app == foreAbility.GetBundleName()) ? element.GetPagePath() : "";
        } else {
            info.abilityName_ = "";
            info.pagePath_ = element.GetPagePath();
        }
    }

    static void InflateWindowInfo(AccessibilityWindowInfo& node, Window& info, bool needAbilityInfo)
    {
        info.focused_ = node.IsFocused();
        info.actived_ = node.IsActive();
        info.decoratorEnabled_ = node.IsDecorEnable();
        AccessibilityElementInfo element;
        LOG_D("Start Get Bundle Name by WindowId %{public}d", node.GetWindowId());
        if (AccessibilityUITestAbility::GetInstance()->GetRootByWindow(node, element) != RET_OK) {
            LOG_E("Failed Get Bundle Name by WindowId %{public}d", node.GetWindowId());
        } else {
            std::string app = element.GetBundleName();
            LOG_I("End Get Bundle Name by WindowId %{public}d, app is %{public}s", node.GetWindowId(), app.data());
            info.bundleName_ = app;
            InflateAbilityInfo(info, app, element, needAbilityInfo);
        }
        auto touchAreas = node.GetTouchHotAreas();
        for (auto area : touchAreas) {
            Rect rect { info.bounds_.left_ + area.GetLeftTopXScreenPostion(),
                        info.bounds_.left_ + area.GetRightBottomXScreenPostion(),
                        info.bounds_.top_  + area.GetLeftTopYScreenPostion(),
                        info.bounds_.top_  + area.GetRightBottomYScreenPostion() };
            info.touchHotAreas_.push_back(rect);
        }
        if (node.GetBundleName().find("SCBMiniCover") == 0) {
            info.touchHotAreas_.clear();
            info.touchHotAreas_.push_back(Rect(0, 0, 0, 0));
        }
        info.mode_ = ConvertWindowMode(node.GetWindowMode());
    }

    static bool GetAamsWindowInfos(vector<AccessibilityWindowInfo> &windows, int32_t displayId,
        bool skipWaitForUiSteady)
    {
        LOG_D("Get Window root info in display %{public}d", displayId);
        auto ability = AccessibilityUITestAbility::GetInstance();
        if (!skipWaitForUiSteady) {
            LOG_D("Wait scroll compelete");
            g_monitorInstance_->WaitScrollCompelete();
        }
        auto ret = ability->GetWindows(displayId, windows);
        if (ret != RET_OK) {
            LOG_W("GetWindows in display %{public}d from AccessibilityUITestAbility failed, ret: %{public}d",
                displayId, ret);
            return false;
        }
        auto foldStatus = DisplayManager::GetInstance().GetFoldStatus();
        if (foldStatus == Rosen::FoldStatus::HALF_FOLD && displayId == 0) {
            vector<AccessibilityWindowInfo> windowsInVirtual;
            auto ret1 = ability->GetWindows(VIRTUAL_DISPLAY_ID, windowsInVirtual);
            LOG_D("GetWindows in display 999 from AccessibilityUITestAbility, ret: %{public}d", ret1);
            for (auto &win : windowsInVirtual) {
                windows.emplace_back(win);
            }
        }
        if (windows.empty()) {
            LOG_E("Get Windows in display %{public}d failed", displayId);
            return false;
        }
        sort(windows.begin(), windows.end(), [](auto &w1, auto &w2) -> bool {
            return w1.GetWindowLayer() > w2.GetWindowLayer();
        });
        LOG_D("End Get Window root info");
        return true;
    }

    static void UpdateWindowAttrs(Window &win, std::vector<Rect> &overplays)
    {
        for (const auto &overWin : overplays) {
            Rect intersectionRect{0, 0, 0, 0};
            if (RectAlgorithm::ComputeIntersection(win.bounds_, overWin, intersectionRect)) {
                win.invisibleBoundsVec_.emplace_back(overWin);
            }
        }
        RectAlgorithm::ComputeMaxVisibleRegion(win.bounds_, overplays, win.visibleBounds_);
        if (win.touchHotAreas_.empty()) {
            overplays.emplace_back(win.bounds_);
        } else {
            std::string touchAreaInfo;
            for (auto rect : win.touchHotAreas_) {
                touchAreaInfo += rect.Describe() + " ";
                overplays.emplace_back(rect);
            }
            LOG_I("window %{public}d touchArea: %{public}s", win.id_, touchAreaInfo.c_str());
        }
        if (win.displayId_ == VIRTUAL_DISPLAY_ID) {
            win.offset_ = Point(0, GetFoldArea().bottom_);
        }
    }

    void SysUiController::GetUiWindows(std::map<int32_t, vector<Window>> &out, int32_t targetDisplay,
        bool skipWaitForUiSteady, bool needAbilityInfo)
    {
        std::lock_guard<std::mutex> dumpLocker(dumpMtx); // disallow concurrent dumpUi
        ApiCallErr error = ApiCallErr(NO_ERROR);
        if (!connected_ && !ConnectToSysAbility(error)) {
            LOG_E("%{public}s", error.message_.c_str());
            return;
        }
        DisplayManager &dpm = DisplayManager::GetInstance();
        auto displayIds = dpm.GetAllDisplayIds();
        for (auto displayId : displayIds) {
            if ((targetDisplay != -1 && targetDisplay != static_cast<int32_t>(displayId)) ||
                displayId == VIRTUAL_DISPLAY_ID) {
                continue;
            }
            if (!ConvertAAMS(displayId, error)) {
                continue;
            }
            vector<AccessibilityWindowInfo> windows;
            if (!GetAamsWindowInfos(windows, displayId, skipWaitForUiSteady)) {
                continue;
            }
            auto screenSize = GetDisplaySize(displayId);
            auto screenRect = Rect(0, screenSize.px_, 0, screenSize.py_);
            std::vector<Window> winInfos;
            std::vector<Rect> overplays;
            // window wrapper
            for (auto &win : windows) {
                Rect winRectInScreen = GetVisibleRect(screenRect, win);
                Rect visibleArea = winRectInScreen;
                if (!RectAlgorithm::ComputeMaxVisibleRegion(winRectInScreen, overplays, visibleArea)) {
                    LOG_I("window is covered, windowId : %{public}d, layer is %{public}d", win.GetWindowId(),
                          win.GetWindowLayer());
                    continue;
                }
                LOG_I("window is visible, windowId: %{public}d, active: %{public}d, focus: %{public}d,"
                    "layer: %{public}d, displayId: %{public}" PRIu64 "",
                    win.GetWindowId(), win.IsActive(), win.IsFocused(), win.GetWindowLayer(), win.GetDisplayId());
                Window winWrapper{win.GetWindowId()};
                winWrapper.bounds_ = winRectInScreen;
                InflateWindowInfo(win, winWrapper, needAbilityInfo);
                winWrapper.displayId_ = win.GetDisplayId();
                UpdateWindowAttrs(winWrapper, overplays);
                winWrapper.displayId_ = displayId;
                winInfos.emplace_back(move(winWrapper));
            }
            out.insert(make_pair(displayId, move(winInfos)));
        }
    }

    bool SysUiController::GetWidgetsInWindow(const Window &winInfo, unique_ptr<ElementNodeIterator> &elementIterator,
        AamsWorkMode mode)
    {
        std::lock_guard<std::mutex> dumpLocker(dumpMtx); // disallow concurrent dumpUi
        ApiCallErr error = ApiCallErr(NO_ERROR);
        if (!ConvertAAMS(winInfo.displayId_, error)) {
            return false;
        }
        if (!connected_) {
            LOG_W("Connect to AccessibilityUITestAbility failed");
            return false;
        }
        std::vector<AccessibilityElementInfo> elementInfos;
        AccessibilityWindowInfo window;
        LOG_D("Get Window by WindowId %{public}d", winInfo.id_);
        if (AccessibilityUITestAbility::GetInstance()->GetWindow(winInfo.id_, window) != RET_OK) {
            LOG_E("GetWindowInfo failed, windowId: %{public}d", winInfo.id_);
            return false;
        }
        LOG_D("Start Get nodes from window by WindowId %{public}d", winInfo.id_);
        auto ret = RET_ERR_FAILED;
        auto ability = AccessibilityUITestAbility::GetInstance();
        if (mode == AamsWorkMode::FASTGETNODE) {
            LOG_D("GetRootByWindowBatch in reduced mode");
            ret = ability->GetRootByWindowBatch(window, elementInfos, false, true);
        } else {
            ret = ability->GetRootByWindowBatch(window, elementInfos);
        }
        if (ret != RET_OK) {
            LOG_E("GetRootByWindowBatch failed, windowId: %{public}d", winInfo.id_);
            return false;
        } else {
            LOG_I("End Get nodes from window by WindowId %{public}d, node size is %{public}zu, appId: %{public}s",
                  winInfo.id_, elementInfos.size(), winInfo.bundleName_.data());
            elementIterator = std::make_unique<ElementNodeIteratorImpl>(elementInfos);
        }
        return true;
    }

    int32_t SysUiController::GetValidDisplayId(int32_t id) const
    {
        if (id == UNASSIGNED) {
            id = DisplayManager::GetInstance().GetDefaultDisplayId();
        }
        return id;
    }

    bool SysUiController::CheckDisplayExist(int32_t displayId) const
    {
        if (displayId == UNASSIGNED) {
            return true;
        }
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        return displayMgr.GetDisplayById(displayId) != nullptr;
    }

    static void SetItemByType(PointerEvent::PointerItem &pinterItem, const PointerMatrix &events,
        uint32_t finger, bool pressed, const Point &point)
    {
        switch (events.GetToolType()) {
            case TouchToolType::FINGER:
                pinterItem.SetToolType(PointerEvent::TOOL_TYPE_FINGER);
                if (events.GetTouchPressure() != 0.0) {
                    pinterItem.SetPressure(events.GetTouchPressure());
                }
                break;
            case TouchToolType::PEN:
                pinterItem.SetToolType(PointerEvent::TOOL_TYPE_PEN);
                pinterItem.SetPressure(events.GetTouchPressure());
                break;
            case TouchToolType::KNUCKLE:
                pinterItem.SetToolType(PointerEvent::TOOL_TYPE_KNUCKLE);
                break;
            default:
                return;
        }
        pinterItem.SetPointerId(finger);
        pinterItem.SetOriginPointerId(finger);
        pinterItem.SetDisplayX(point.px_);
        pinterItem.SetDisplayY(point.py_);
        pinterItem.SetRawDisplayX(point.px_);
        pinterItem.SetRawDisplayY(point.py_);
        pinterItem.SetPressed(pressed);
        LOG_I("Add touchItem, finger:%{public}d, pressed:%{public}d, location:%{public}d, %{public}d",
            finger, pressed, point.px_, point.py_);
    }

    static void AddPointerItems(PointerEvent &event, const vector<pair<bool, Point>> &fingerStatus,
        const PointerMatrix &events, uint32_t currentFinger, uint32_t currentStep)
    {
        if (events.IsSyncInject()) {
            for (auto finger = 0; finger < events.GetFingers(); finger++) {
                PointerEvent::PointerItem pinterItem;
                SetItemByType(pinterItem, events, finger, fingerStatus[finger].first,
                    events.At(finger, currentStep).point_);
                event.UpdatePointerItem(finger, pinterItem);
            }
        } else {
            PointerEvent::PointerItem pinterItem;
            SetItemByType(pinterItem, events, currentFinger, fingerStatus[currentFinger].first,
                fingerStatus[currentFinger].second);
            event.UpdatePointerItem(currentFinger, pinterItem);
            // update pinterItem of other fingers which in pressed state.
            for (uint32_t index = 0; index < fingerStatus.size(); index++) {
                if (index == currentFinger) {
                    continue;
                }
                if (fingerStatus[index].first) {
                    PointerEvent::PointerItem pinterItemForOther;
                    SetItemByType(pinterItemForOther, events, index, true, fingerStatus[index].second);
                    event.UpdatePointerItem(index, pinterItemForOther);
                }
            }
        }
    }

    void SysUiController::InjectTouchEventSequence(const PointerMatrix &events) const
    {
        // fingerStatus stores the press status and coordinates of each finger.
        vector<pair<bool, Point>> fingerStatus(events.GetFingers(), make_pair(false, Point(0,0)));
        for (uint32_t step = 0; step < events.GetSteps(); step++) {
            for (uint32_t finger = 0; finger < events.GetFingers(); finger++) {
                auto pointerEvent = PointerEvent::Create();
                if (pointerEvent == nullptr) {
                    LOG_E("Creat PointerEvent failed.");
                    return;
                }
                bool isPressed = (events.At(finger, step).stage_ == ActionStage::DOWN) ||
                                 (events.At(finger, step).stage_ == ActionStage::MOVE);
                fingerStatus[finger] = make_pair(isPressed, events.At(finger, step).point_);
                pointerEvent->SetPointerId(finger);
                switch (events.At(finger, step).stage_) {
                    case ActionStage::DOWN:
                        pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_DOWN);
                        break;
                    case ActionStage::MOVE:
                        pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_MOVE);
                        break;
                    case ActionStage::UP:
                        pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_UP);
                        break;
                    case ActionStage::PROXIMITY_IN:
                        pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_PROXIMITY_IN);
                        break;
                    case ActionStage::PROXIMITY_OUT:
                        pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_PROXIMITY_OUT);
                        break;
                    default:
                        return;
                }
                AddPointerItems(*pointerEvent, fingerStatus, events, finger, step);
                pointerEvent->SetSourceType(PointerEvent::SOURCE_TYPE_TOUCHSCREEN);
                auto displayId = GetValidDisplayId(events.At(finger, step).point_.displayId_);
                pointerEvent->SetTargetDisplayId(displayId);
                InputManager::GetInstance()->SimulateInputEvent(pointerEvent, false);
                LOG_D("Inject touchEvent to display : %{public}d", displayId);
                if (events.At(finger, step).holdMs_ > 0) {
                    this_thread::sleep_for(chrono::milliseconds(events.At(finger, step).holdMs_));
                }
            }
        }
    }

    static void SetMousePointerItemAttr(const MouseEvent &event, PointerEvent::PointerItem &item)
    {
        item.SetPointerId(0);
        item.SetOriginPointerId(0);
        item.SetToolType(event.touchToolType);
        item.SetDisplayX(event.point_.px_);
        item.SetDisplayY(event.point_.py_);
        item.SetRawDisplayX(event.point_.px_);
        item.SetRawDisplayY(event.point_.py_);
        item.SetRawDx(event.rawDelta.px_);
        item.SetRawDy(event.rawDelta.py_);
        item.SetPressed(false);
        item.SetDownTime(0);
        LOG_I("Inject mouseEvent, pressed:%{public}d, location:%{public}d, %{public}d, delta:%{public}d, %{public}d",
            event.stage_ == ActionStage::DOWN, event.point_.px_, event.point_.py_,
            event.rawDelta.px_, event.rawDelta.py_);
    }

    static void SetMousePointerEventAttr(shared_ptr<PointerEvent> pointerEvent, const MouseEvent &event)
    {
        pointerEvent->SetSourceType(PointerEvent::SOURCE_TYPE_MOUSE);
        pointerEvent->SetPointerId(0);
        if (event.button_ != MouseButton::BUTTON_NONE) {
            pointerEvent->SetButtonId(event.button_);
            if ((event.stage_ == ActionStage::DOWN || event.stage_ == ActionStage::MOVE)) {
                pointerEvent->SetButtonPressed(event.button_);
            } else if (event.stage_ == ActionStage::UP) {
                pointerEvent->DeleteReleaseButton(event.button_);
            }
        }
    }

    static void SetMousePointerAction(shared_ptr<PointerEvent> pointerEvent, const ActionStage stage,
                                      PointerEvent::PointerItem &item)
    {
        constexpr double axialValue = 15;
        switch (stage) {
            case ActionStage::DOWN:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_BUTTON_DOWN);
                item.SetPressed(true);
                break;
            case ActionStage::MOVE:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_MOVE);
                break;
            case ActionStage::UP:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_BUTTON_UP);
                break;
            case ActionStage::AXIS_BEGIN:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_BEGIN);
                break;
            case ActionStage::AXIS_UP:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_UPDATE);
                pointerEvent->SetAxisValue(OHOS::MMI::PointerEvent::AXIS_TYPE_SCROLL_VERTICAL, -axialValue);
                break;
            case ActionStage::AXIS_DOWN:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_UPDATE);
                pointerEvent->SetAxisValue(OHOS::MMI::PointerEvent::AXIS_TYPE_SCROLL_VERTICAL, axialValue);
                break;
            case ActionStage::AXIS_LEFT:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_UPDATE);
                pointerEvent->SetAxisValue(OHOS::MMI::PointerEvent::AXIS_TYPE_SCROLL_HORIZONTAL, -axialValue);
                break;
            case ActionStage::AXIS_RIGHT:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_UPDATE);
                pointerEvent->SetAxisValue(OHOS::MMI::PointerEvent::AXIS_TYPE_SCROLL_HORIZONTAL, axialValue);
                break;
            case ActionStage::AXIS_STOP:
                pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_AXIS_END);
                break;
            default:
                return;
        }
    }

    void SysUiController::InjectMouseEvent(const MouseEvent &event) const
    {
        auto pointerEvent = PointerEvent::Create();
        if (pointerEvent == nullptr || event.stage_ == ActionStage::NONE) {
            return;
        }
        PointerEvent::PointerItem item;
        SetMousePointerEventAttr(pointerEvent, event);
        SetMousePointerAction(pointerEvent, event.stage_, item);
        SetMousePointerItemAttr(event, item);
        pointerEvent->AddPointerItem(item);
        auto displayId = GetValidDisplayId(event.point_.displayId_);
        pointerEvent->SetTargetDisplayId(displayId);
        if (!downKeys_.empty()) {
            pointerEvent->SetPressedKeys(downKeys_);
        }
        InputManager::GetInstance()->SimulateInputEvent(pointerEvent, false);
        LOG_I("Inject mouseEvent to display : %{public}d", displayId);
        this_thread::sleep_for(chrono::milliseconds(event.holdMs_));
    }

    void SysUiController::InjectMouseEventSequence(const vector<MouseEvent> &events) const
    {
        for (auto &event : events) {
            auto keyEvents = event.keyEvents_;
            if (!keyEvents.empty() && keyEvents.front().stage_ == ActionStage::DOWN) {
                InjectKeyEventSequence(keyEvents, event.point_.displayId_);
                InjectMouseEvent(event);
            } else {
                InjectMouseEvent(event);
                InjectKeyEventSequence(keyEvents, event.point_.displayId_);
            }
        }
    }

    void SysUiController::InjectKeyEventSequence(const vector<KeyEvent> &events, int32_t displayId) const
    {
        displayId = GetValidDisplayId(displayId);
        for (auto &event : events) {
            if (event.code_ == KEYCODE_NONE) {
                continue;
            }
            auto keyEvent = OHOS::MMI::KeyEvent::Create();
            if (keyEvent == nullptr) {
                LOG_E("Creat KeyEvent failed.");
                return;
            }
            if (event.stage_ == ActionStage::UP) {
                auto iter = std::find(downKeys_.begin(), downKeys_.end(), event.code_);
                if (iter == downKeys_.end()) {
                    LOG_W("Cannot release a not-pressed key: %{public}d", event.code_);
                    continue;
                }
                downKeys_.erase(iter);
                keyEvent->SetKeyCode(event.code_);
                keyEvent->SetKeyAction(OHOS::MMI::KeyEvent::KEY_ACTION_UP);
                OHOS::MMI::KeyEvent::KeyItem keyItem;
                keyItem.SetKeyCode(event.code_);
                keyItem.SetPressed(false);
                keyEvent->AddKeyItem(keyItem);
                keyEvent->SetTargetDisplayId(displayId);
                InputManager::GetInstance()->SimulateInputEvent(keyEvent);
                LOG_I("Inject keyEvent up, keycode:%{public}d", event.code_);
            } else {
                downKeys_.push_back(event.code_);
                for (auto downKey : downKeys_) {
                    keyEvent->SetKeyCode(downKey);
                    keyEvent->SetKeyAction(OHOS::MMI::KeyEvent::KEY_ACTION_DOWN);
                    OHOS::MMI::KeyEvent::KeyItem keyItem;
                    keyItem.SetKeyCode(downKey);
                    keyItem.SetPressed(true);
                    keyEvent->AddKeyItem(keyItem);
                }
                keyEvent->SetTargetDisplayId(displayId);
                InputManager::GetInstance()->SimulateInputEvent(keyEvent);
                LOG_I("Inject keyEvent down, keycode:%{public}d", event.code_);
                if (event.holdMs_ > 0) {
                    this_thread::sleep_for(chrono::milliseconds(event.holdMs_));
                }
            }
        }
        // check not released keys
        for (auto downKey : downKeys_) {
            LOG_W("Key event sequence injections done with not-released key: %{public}d", downKey);
        }
    }

    bool SysUiController::IsTouchPadExist() const
    {
        std::vector<int32_t> inputDeviceIdList;
        auto getDeviceIdsCallback = [&inputDeviceIdList](std::vector<int32_t>& deviceIds) {
            inputDeviceIdList = deviceIds;
        };
        int32_t ret1 = InputManager::GetInstance()->GetDeviceIds(getDeviceIdsCallback);
        if (ret1 != RET_OK) {
            LOG_E("Get device ids failed");
            return false;
        }
        const int32_t touchPadTag = 1 << 3;
        for (auto inputDeviceId : inputDeviceIdList) {
            std::shared_ptr<MMI::InputDevice> inputDevice;
            auto getDeviceCallback = [&inputDevice](std::shared_ptr<MMI::InputDevice> device) {
                inputDevice = device;
            };
            int32_t ret2 = MMI::InputManager::GetInstance()->GetDevice(inputDeviceId, getDeviceCallback);
            if (ret2 != RET_OK || inputDevice == nullptr) {
                LOG_E("Get device failed");
                continue;
            }
            if (inputDevice->GetType() & touchPadTag) {
                return true;
            }
        }
        return false;
    }

    void SysUiController::InjectTouchPadEventSequence(const vector<TouchPadEvent>& events) const
    {
        vector<pair<bool, Point>> fingerStatus(1, make_pair(false, Point(0, 0)));
        for (auto &event : events) {
            auto pointerEvent = PointerEvent::Create();
            if (pointerEvent == nullptr) {
                LOG_E("Creat PointerEvent failed.");
                return;
            }
            pointerEvent->SetPointerId(0);
            switch (event.stage) {
                case ActionStage::DOWN:
                    pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_SWIPE_BEGIN);
                    break;
                case ActionStage::MOVE:
                    pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_SWIPE_UPDATE);
                    break;
                case ActionStage::UP:
                    pointerEvent->SetPointerAction(PointerEvent::POINTER_ACTION_SWIPE_END);
                    break;
                default:
                    break;
            }
            fingerStatus[0] = make_pair(false, event.point);
            PointerMatrix pointer;
            AddPointerItems(*pointerEvent, fingerStatus, pointer, 0, 0);
            pointerEvent->SetSourceType(PointerEvent::SOURCE_TYPE_TOUCHPAD);
            pointerEvent->SetFingerCount(event.fingerCount);
            auto displayId = GetValidDisplayId(event.point.displayId_);
            pointerEvent->SetTargetDisplayId(displayId);
            InputManager::GetInstance()->SimulateInputEvent(pointerEvent, false);
            LOG_D("Inject touchEvent to display %{public}d", displayId);
            if (event.holdMs > 0) {
                this_thread::sleep_for(chrono::milliseconds(event.holdMs));
            }
        }
    }

    void SysUiController::PutTextToClipboard(string_view text, ApiCallErr &error) const
    {
        auto ret = OHOS::testserver::TestServerClient::GetInstance().SetPasteData(string(text));
        if (ret != OHOS::testserver::TEST_SERVER_OK) {
            LOG_E("Set pasteBoard data failed.");
            error = ApiCallErr(ERR_INTERNAL, "Set pasteBoard data failed.");
            if (ret == OHOS::testserver::TEST_SERVER_NOT_SUPPORTED) {
                error.code_ = ERR_NO_SYSTEM_CAPABILITY;
            }
            return;
        }
        static constexpr auto sliceMs = 500;
        this_thread::sleep_for(chrono::milliseconds(sliceMs));
    }

    void SysUiController::ChangeWindowMode(int32_t windowId, WindowMode mode) const
    {
        switch (mode) {
            case WindowMode::FULLSCREEN:
                OHOS::testserver::TestServerClient::GetInstance().ChangeWindowMode(windowId,
                    static_cast<uint32_t>(OHOS::Rosen::WindowMode::WINDOW_MODE_FULLSCREEN));
                break;
            case WindowMode::SPLIT_PRIMARY:
                OHOS::testserver::TestServerClient::GetInstance().ChangeWindowMode(windowId,
                    static_cast<uint32_t>(OHOS::Rosen::WindowMode::WINDOW_MODE_SPLIT_PRIMARY));
                break;
            case WindowMode::FLOATING:
                OHOS::testserver::TestServerClient::GetInstance().ChangeWindowMode(windowId,
                    static_cast<uint32_t>(OHOS::Rosen::WindowMode::WINDOW_MODE_FLOATING));
                break;
            case WindowMode::MINIMIZED:
                OHOS::testserver::TestServerClient::GetInstance().MinimizeWindow(windowId);
                break;
            case WindowMode::CLOSED:
                OHOS::testserver::TestServerClient::GetInstance().TerminateWindow(windowId);
                break;
            default:
                break;
        }
        static constexpr auto sliceMs = 500;
        this_thread::sleep_for(chrono::milliseconds(sliceMs));
    }

    bool SysUiController::IsWorkable() const
    {
        return connected_;
    }

    bool SysUiController::IsWearable() const
    {
        bool isWearable = false;
#ifdef ARKXTEST_WATCH_FEATURE_ENABLE
        isWearable = true;
#endif
        return isWearable;
    }

    bool SysUiController::IsPenKeySupported(bool shouldConnectPen) const
    {
        bool res = false;
#ifdef ARKXTEST_TRIGGER_PEN_KEY_ENABLE
        res = true;
#endif
        return res;
    }

    bool SysUiController::IsAdjustWindowModeEnable() const
    {
        bool IsAdjustWindowModeEnable = false;
#ifdef ARKXTEST_ADJUST_WINDOWMODE_ENABLE
        IsAdjustWindowModeEnable = true;
#endif
        return IsAdjustWindowModeEnable;
    }

    bool SysUiController::GetCharKeyCode(char ch, int32_t &code, int32_t &ctrlCode) const
    {
        ctrlCode = KEYCODE_NONE;
        if (ch >= 'a' && ch <= 'z') {
            code = OHOS::MMI::KeyEvent::KEYCODE_A + static_cast<int32_t>(ch - 'a');
        } else if (ch >= 'A' && ch <= 'Z') {
            ctrlCode = OHOS::MMI::KeyEvent::KEYCODE_SHIFT_LEFT;
            code = OHOS::MMI::KeyEvent::KEYCODE_A + static_cast<int32_t>(ch - 'A');
        } else if (ch >= '0' && ch <= '9') {
            code = OHOS::MMI::KeyEvent::KEYCODE_0 + static_cast<int32_t>(ch - '0');
        } else if (SingleKeySymbalMap.find(ch) != SingleKeySymbalMap.end()) {
            code = SingleKeySymbalMap.find(ch)->second;
        } else if (MultiKeySymbalMap.find(ch) != MultiKeySymbalMap.end()) {
            ctrlCode = OHOS::MMI::KeyEvent::KEYCODE_SHIFT_LEFT;
            code = MultiKeySymbalMap.find(ch)->second;
        } else {
            return false;
        }
        return true;
    }

    bool SysUiController::TakeScreenCap(int32_t fd, std::stringstream &errReceiver, int32_t displayId, Rect rect) const
    {
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        displayId = GetValidDisplayId(displayId);
        // get PixelMap from DisplayManager API
        shared_ptr<PixelMap> pixelMap;
        if (rect.GetWidth() == 0) {
            pixelMap = displayMgr.GetScreenshot(displayId);
        } else {
            Media::Rect region = {.left = rect.left_, .top = rect.top_,
                .width = rect.right_ - rect.left_, .height = rect.bottom_ - rect.top_};
            Media::Size size = {.width = rect.right_ - rect.left_, .height = rect.bottom_ - rect.top_};
            pixelMap = displayMgr.GetScreenshot(displayId, region, size, 0);
        }
        if (pixelMap == nullptr) {
            errReceiver << "Failed to get display pixelMap";
            return false;
        }
        int64_t packedSize = 0L;
        auto pixelSize = static_cast<uint32_t>(pixelMap->GetByteCount());
        LOG_D("PixelSize: %{public}d", pixelSize);
        auto buffer = new (std::nothrow) uint8_t[pixelSize];
        Media::ImagePacker imagePacker;
        Media::PackOption packOption;
        packOption.format = "image/png";
        imagePacker.StartPacking(buffer, pixelSize, packOption);
        imagePacker.AddImage(*pixelMap);
        uint32_t packResult = imagePacker.FinalizePacking(packedSize);
        LOG_D("Packed pixelMap, packResult: %{public}d", packResult);
        LOG_D("Packed pixelMap, packedSize: %{public}" PRId64, packedSize);
        if (packResult != NO_ERROR || buffer == nullptr) {
            delete[] buffer;
            return false;
        }
        int ret = write(fd, buffer, packedSize);
        if (ret == -1) {
            int err = errno;
            LOG_E("write failed: %{public}d", err);
            LOG_E("write failed reason: %{public}s", strerror(err));
            return false;
        }
        delete[] buffer;
        return true;
    }

    bool SysUiController::ConnectToSysAbility(ApiCallErr &error)
    {
        if (connected_) {
            return true;
        }
        mutex mtx;
        unique_lock<mutex> uLock(mtx);
        std::shared_ptr<condition_variable> condition = make_shared<condition_variable>();
        auto onConnectCallback = [condition]() {
            LOG_I("Success connect to AccessibilityUITestAbility");
            condition->notify_all();
        };
        auto onDisConnectCallback = [this]() { this->connected_ = false; };
        if (g_monitorInstance_ == nullptr) {
            g_monitorInstance_ = make_shared<UiEventMonitor>();
        }
        g_monitorInstance_->SetOnAbilityConnectCallback(onConnectCallback);
        g_monitorInstance_->SetOnAbilityDisConnectCallback(onDisConnectCallback);
        auto ability = AccessibilityUITestAbility::GetInstance();
        if (ability->RegisterAbilityListener(g_monitorInstance_) != RET_OK) {
            error = ApiCallErr(ERR_INITIALIZE_FAILED, "Can not connect to AAMS, REGISTER_LISTENER_FAILED");
            return false;
        }
        auto ret = RET_OK;
        if (isSingleUser_ || currentUser_ == -1) {
            ret = ability->Connect();
        } else {
            ret = ability->Connect(currentUser_);
        }
        LOG_I("Connect to AAMS in user %{public}d, result: %{public}d", currentUser_, ret);
        if (ret != RET_OK) {
            error = ApiCallErr(ERR_INITIALIZE_FAILED, "Can not connect to AAMS");
            if (ret == RET_ERR_CONNECTION_EXIST) {
                error.message_ += ", RET_ERR_CONNECTION_EXIST";
            } else {
                error.message_ += ", RET_ERR_AAMS";
            }
            return false;
        }
        const auto timeout = chrono::milliseconds(7000);
        if (condition->wait_for(uLock, timeout) == cv_status::timeout) {
            LOG_E("Wait connection to AccessibilityUITestAbility timed out");
            error = ApiCallErr(ERR_INITIALIZE_FAILED, "Can not connect to AAMS, RET_TIMEOUT");
            return false;
        }
        connected_ = true;
        currentUser_ = ability->GetCurrentUserId();
        LOG_I("connect to AAMS in user %{public}d successful", currentUser_);
        return true;
    }

    void SysUiController::RegisterUiEventListener(std::shared_ptr<UiEventListener> listener) const
    {
        g_monitorInstance_->RegisterUiEventListener(listener);
    }

    bool SysUiController::WaitForUiSteady(uint32_t idleThresholdMs, uint32_t timeoutMs) const
    {
        return g_monitorInstance_->WaitEventIdle(idleThresholdMs, timeoutMs);
    }

    void SysUiController::DisConnectFromSysAbility()
    {
        if (!connected_ || g_monitorInstance_ == nullptr) {
            return;
        }
        connected_ = false;
        mutex mtx;
        unique_lock<mutex> uLock(mtx);
        condition_variable condition;
        auto onDisConnectCallback = [&condition]() {
            LOG_I("Success disconnect from AccessibilityUITestAbility");
            condition.notify_all();
        };
        g_monitorInstance_->SetOnAbilityDisConnectCallback(onDisConnectCallback);
        auto ability = AccessibilityUITestAbility::GetInstance();
        LOG_I("Start disconnect from AccessibilityUITestAbility in user %{public}d", currentUser_);
        auto ret = ability->Disconnect(currentUser_);
        if (ret != RET_OK) {
            LOG_E("Failed to disconnect from AccessibilityUITestAbility, ret: %{public}d", ret);
            return;
        }
        const auto timeout = chrono::milliseconds(200);
        if (condition.wait_for(uLock, timeout) == cv_status::timeout) {
            LOG_E("Wait disconnection from AccessibilityUITestAbility timed out");
            return;
        }
    }

    void SysUiController::SetDisplayRotation(DisplayRotation rotation) const
    {
        auto display = DisplayManager::GetInstance().GetDefaultDisplay();
        if (display == nullptr) {
            LOG_E("DisplayManager init fail");
            return;
        }
        auto screenId = display->GetScreenId();
        ScreenManager &screenMgr = ScreenManager::GetInstance();
        bool isLocked = false;
        screenMgr.IsScreenRotationLocked(isLocked);
        if (isLocked) {
            screenMgr.SetScreenRotationLocked(false);
        }
        auto screen = screenMgr.GetScreenById(screenId);
        if (screen == nullptr) {
            LOG_E("ScreenManager init fail");
            return;
        }
        switch (rotation) {
            case ROTATION_0 :
                screen->SetOrientation(Orientation::VERTICAL);
                break;
            case ROTATION_90 :
                screen->SetOrientation(Orientation::HORIZONTAL);
                break;
            case ROTATION_180 :
                screen->SetOrientation(Orientation::REVERSE_VERTICAL);
                break;
            case ROTATION_270 :
                screen->SetOrientation(Orientation::REVERSE_HORIZONTAL);
                break;
            default :
                break;
        }
    }

    DisplayRotation SysUiController::GetDisplayRotation(int32_t displayId) const
    {
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        displayId = GetValidDisplayId(displayId);
        auto display = displayMgr.GetDisplayById(displayId);
        if (display == nullptr) {
            LOG_E("DisplayManager init fail");
            return DisplayRotation::ROTATION_0;
        }
        auto rotation = (DisplayRotation)display->GetRotation();
        return rotation;
    }

    void SysUiController::SetDisplayRotationEnabled(bool enabled) const
    {
        ScreenManager &screenMgr = ScreenManager::GetInstance();
        screenMgr.SetScreenRotationLocked(!enabled);
    }

    Point SysUiController::GetDisplaySize(int32_t displayId) const
    {
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        displayId = GetValidDisplayId(displayId);
        auto display = displayMgr.GetDisplayById(displayId);
        if (display == nullptr) {
            LOG_E("DisplayManager init fail");
            return {0, 0, displayId};
        }
        auto width = display->GetWidth();
        auto height = display->GetHeight();
        LOG_D("GetDisplaysize in display %{public}d, width: %{public}d, height: %{public}d", displayId, width, height);
        auto foldStatus = displayMgr.GetFoldStatus();
        if (foldStatus == Rosen::FoldStatus::HALF_FOLD && displayId == 0) {
            auto virtualDisplay = displayMgr.GetDisplayById(VIRTUAL_DISPLAY_ID);
            if (virtualDisplay != nullptr) {
                auto virtualwidth = virtualDisplay->GetWidth();
                auto virtualheight = virtualDisplay->GetHeight();
                auto foldArea = GetFoldArea();
                auto foldAreaWidth = foldArea.right_ - foldArea.left_;
                auto foldAreaHeight = foldArea.bottom_ - foldArea.top_;
                LOG_D("GetDisplaysize in virtual display, width: %{public}d, height: %{public}d",
                    virtualwidth, virtualheight);
                LOG_D("GetDisplaysize in foldArea, width: %{public}d, height: %{public}d", foldAreaWidth,
                    foldAreaHeight);
                height = height + virtualheight + foldAreaHeight;
            }
        }
        Point result(width, height, displayId);
        return result;
    }

    Point SysUiController::ConvertRelativeToGlobal(const Point &point, ApiCallErr &error) const
    {
        using namespace Rosen;
        int32_t displayId = GetValidDisplayId(point.displayId_);
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        auto display = displayMgr.GetDisplayById(displayId);
        if (display == nullptr) {
            error = ApiCallErr(ERR_INVALID_INPUT, "Display not found.");
            return Point(0, 0, displayId);
        }

        Rosen::RelativePosition relativePos = {displayId, {point.px_, point.py_}};
        Rosen::Position position;
        Rosen::DMError ret = displayMgr.ConvertRelativeCoordinateToGlobal(relativePos, position);
        if (ret != DMError::DM_OK) {
            error = ApiCallErr(ERR_INTERNAL, "Failed to convert coordinate to global.");
            return Point(0, 0, displayId);
        }

        Point globalPoint(position.x, position.y, displayId);
        return globalPoint;
    }

    Point SysUiController::GetDisplayDensity(int32_t displayId) const
    {
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        displayId = GetValidDisplayId(displayId);
        auto display = displayMgr.GetDisplayById(displayId);
        if (display == nullptr) {
            LOG_E("DisplayManager init fail");
            return {0, 0, displayId};
        }
        auto rate = display->GetVirtualPixelRatio();
        Point displaySize = GetDisplaySize(displayId);
        Point result(displaySize.px_ * rate, displaySize.py_ * rate, displayId);
        return result;
    }

    bool SysUiController::IsScreenOn() const
    {
        DisplayManager &displayMgr = DisplayManager::GetInstance();
        auto displayId = displayMgr.GetDefaultDisplayId();
        auto state = displayMgr.GetDisplayState(displayId);
        return (state != DisplayState::OFF);
    }

    void SysUiController::CloseAamsEvent() const
    {
        AccessibilityUITestAbility::GetInstance()->ConfigureEvents({ Accessibility::EventType::TYPE_VIEW_INVALID });
    }

    void SysUiController::OpenAamsEvent() const
    {
        AccessibilityUITestAbility::GetInstance()->ConfigureEvents(EVENT_MASK);
    }

    class OnSaLoadCallback : public SystemAbilityLoadCallbackStub {
    public:
        explicit OnSaLoadCallback(mutex &mutex): mutex_(mutex) {};
        ~OnSaLoadCallback() {};
        void OnLoadSystemAbilitySuccess(int32_t systemAbilityId, const sptr<IRemoteObject>& remoteObject) override
        {
            if (systemAbilityId == OHOS::DFX_HI_DUMPER_SERVICE_ABILITY_ID) {
                remoteObject_ = remoteObject;
                mutex_.unlock();
            }
        }
        void OnLoadSystemAbilityFail(int32_t systemAbilityId) override
        {
            if (systemAbilityId == OHOS::DFX_HI_DUMPER_SERVICE_ABILITY_ID) {
                mutex_.unlock();
            }
        }

        sptr<IRemoteObject> GetSaObject()
        {
            return remoteObject_;
        }
        
    private:
        mutex &mutex_;
        sptr<IRemoteObject> remoteObject_ = nullptr;
    };

    static void CreateHidumperCmd(const std::string &windowId, vector<u16string> &result)
    {
        result.emplace_back(u"hidumper");
        result.emplace_back(u"-s");
        result.emplace_back(u"WindowManagerService");
        result.emplace_back(u"-a");
        auto winIdInUtf16 = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(windowId);
        auto arg = u16string(u"-w ").append(winIdInUtf16).append(u" -default -lastpage");
        result.emplace_back(move(arg));
        auto pidInUtf16 = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
            to_string(getpid()));
        result.emplace_back(pidInUtf16);
    }

    void SysUiController::GetHidumperInfo(std::string windowId, char **buf, size_t &len)
    {
#ifdef HIDUMPER_ENABLED
        auto sam = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
        // wati SA start
        constexpr auto delayMs = 2000;
        this_thread::sleep_for(chrono::milliseconds(delayMs));
        if (sam == nullptr) {
            LOG_E("Get samgr failed");
            return;
        }
        auto remoteObject = sam->CheckSystemAbility(OHOS::DFX_HI_DUMPER_SERVICE_ABILITY_ID);
        if (remoteObject == nullptr) {
            mutex lock;
            lock.lock();
            sptr<OnSaLoadCallback> loadCallback = new OnSaLoadCallback(lock);
            if (sam->LoadSystemAbility(OHOS::DFX_HI_DUMPER_SERVICE_ABILITY_ID, loadCallback) != ERR_OK) {
                LOG_E("Schedule LoadSystemAbility failed");
                lock.unlock();
                return;
            }
            LOG_E("Schedule LoadSystemAbility succeed");
            lock.unlock();
            remoteObject = loadCallback->GetSaObject();
            LOG_E("LoadSystemAbility callbacked, result = %{public}s", remoteObject == nullptr ? "FAIL" : "SUCCESS");
        }
        if (remoteObject == nullptr) {
            LOG_E("remoteObject is null");
            return;
        }
        // run dump command
        sptr<IDumpBroker> client = iface_cast<IDumpBroker>(remoteObject);
        if (client == nullptr) {
            LOG_E("IDumpBroker converts failed");
            return;
        }
        auto fd = memfd_create("dummy_file", 2);
        ftruncate(fd, 0);
        vector<u16string> args;
        CreateHidumperCmd(windowId, args);
        client->Request(args, fd);
        auto size = lseek(fd, 0, SEEK_END);
        char *tempBuf = new char[size + 1];
        lseek(fd, 0, SEEK_SET);
        read(fd, tempBuf, size);
        *buf = tempBuf;
        len = size;
        close(fd);
#else
        *buf = nullptr;
        len = 0;
#endif
    }

    bool SysUiController::IsKnuckleSnapshotEnable() const
    {
        string uri = "datashare:///com.ohos.settingsdata/entry/settingsdata/USER_SETTINGSDATA_SECURE_100?Proxy=true";
        string knuckleSnapshotKey = "fingersense_smartshot_enabled";
        auto value = OHOS::testserver::TestServerClient::GetInstance().GetValueFromDataShare(uri, knuckleSnapshotKey);
        LOG_D("key = %{public}s, value = %{public}s", knuckleSnapshotKey.c_str(), value.c_str());
        if (value == "") {
            return true;
        } else {
            return atoi(value.c_str()) != 0;
        }
    }
    bool SysUiController::IsKnuckleRecordEnable() const
    {
        string uri = "datashare:///com.ohos.settingsdata/entry/settingsdata/USER_SETTINGSDATA_SECURE_100?Proxy=true";
        string knuckleRecordKey = "fingersense_screen_recording_enabled";
        auto value = OHOS::testserver::TestServerClient::GetInstance().GetValueFromDataShare(uri, knuckleRecordKey);
        LOG_D("key = %{public}s, value = %{public}s", knuckleRecordKey.c_str(), value.c_str());
        if (value == "") {
            return true;
        } else {
            return atoi(value.c_str()) != 0;
        }
    }

    bool SysUiController::IsPcWindowMode() const
    {
        string uri = "datashare:///com.ohos.settingsdata/entry/settingsdata/USER_SETTINGSDATA_100?Proxy=true";
        string knuckleRecordKey = "window_pcmode_switch_status";
        auto value = OHOS::testserver::TestServerClient::GetInstance().GetValueFromDataShare(uri, knuckleRecordKey);
        LOG_D("key = %{public}s, value = %{public}s", knuckleRecordKey.c_str(), value.c_str());
        if (value == "") {
            return false;
        } else {
            return value == "true";
        }
    }

    void SysUiController::SetActiveUser(int32_t userId)
    {
        LOG_I("Set currentUser_ from %{public}d to %{public}d", currentUser_, userId);
        currentUser_ = userId;
    }

    bool SysUiController::ConvertAAMS(int32_t displayId, ApiCallErr &error)
    {
        if (isSingleUser_) {
            return true;
        }
        int32_t userId = OHOS::testserver::TestServerClient::GetInstance().GetUserIdByDisplayId(displayId);
        LOG_I("display %{public}d belongs to user %{public}d", displayId, userId);
        if (userId == currentUser_) {
            return true;
        }
        if (userId == -1) {
            LOG_E("Invalid userId for displayId %{public}d", displayId);
            return false;
        }
        LOG_W("Switch from user %{public}d to user %{public}d", currentUser_, userId);
        DisConnectFromSysAbility();
        currentUser_ = userId;
        return ConnectToSysAbility(error);
    }
    
} // namespace OHOS::uitest