Integrating Accessibility Through XComponent

For third-party framework platforms that are accessed through XComponent, the NDK provides functions for interoperability with accessibility services, enabling third-party framework components to support basic accessibility features in ArkUI. These include focus acquisition, accessibility node retrieval, and operation response.

For single-instance scenarios, use OH_NativeXComponent_GetNativeAccessibilityProvider to obtain the accessibility provider, and then register the required callback functions ArkUI_AccessibilityProviderCallbacks via OH_ArkUI_AccessibilityProviderRegisterCallback.

For multi-instance scenarios, register callback functions ArkUI_AccessibilityProviderCallbacksWithInstance using OH_ArkUI_AccessibilityProviderRegisterCallbackWithInstance.

In these callbacks, third-party frameworks must handle accessibility actions from the accessibility system and send appropriate accessibility events based on component interactions.

NOTE

This example demonstrates accessibility integration. For the complete implementation, see AccessibilityCapiSample. Once integrated, third-party framework components in XComponent will support accessibility interactions when accessibility features are enabled.

  1. Create a project based on OH_ArkUI_SurfaceHolder for surface lifecycle management in custom rendering (XComponent).

  2. Obtain the accessibility provider and register callbacks (the following uses the multi-instance scenario as an example).

    #include <arkui/native_interface_accessibility.h>
    #include <string>
    #include "common/common.h"
    // For details about the complete implementation, see AccessibilityCapiSample.
    #include "fakenode/fake_node.h"
    // For details about the complete implementation, see AccessibilityCapiSample.
    #include "AccessibilityManager.h"
    
    // ···
    AccessibilityManager::AccessibilityManager()
    {
    // Multi-instance scenario
        accessibilityProviderCallbacksWithInstance_.findAccessibilityNodeInfosById = FindAccessibilityNodeInfosById;
        accessibilityProviderCallbacksWithInstance_.findAccessibilityNodeInfosByText = FindAccessibilityNodeInfosByText;
        accessibilityProviderCallbacksWithInstance_.findFocusedAccessibilityNode = FindFocusedAccessibilityNode;
        accessibilityProviderCallbacksWithInstance_.findNextFocusAccessibilityNode = FindNextFocusAccessibilityNode;
        accessibilityProviderCallbacksWithInstance_.executeAccessibilityAction = ExecuteAccessibilityAction;
        accessibilityProviderCallbacksWithInstance_.clearFocusedFocusAccessibilityNode = ClearFocusedFocusAccessibilityNode;
        accessibilityProviderCallbacksWithInstance_.getAccessibilityNodeCursorPosition = GetAccessibilityNodeCursorPosition;
    // Single-instance scenario
        accessibilityProviderCallbacks_.findAccessibilityNodeInfosById = FindAccessibilityNodeInfosById;
        accessibilityProviderCallbacks_.findAccessibilityNodeInfosByText = FindAccessibilityNodeInfosByText;
        accessibilityProviderCallbacks_.findFocusedAccessibilityNode = FindFocusedAccessibilityNode;
        accessibilityProviderCallbacks_.findNextFocusAccessibilityNode = FindNextFocusAccessibilityNode;
        accessibilityProviderCallbacks_.executeAccessibilityAction = ExecuteAccessibilityAction;
        accessibilityProviderCallbacks_.clearFocusedFocusAccessibilityNode = ClearFocusedFocusAccessibilityNode;
        accessibilityProviderCallbacks_.getAccessibilityNodeCursorPosition = GetAccessibilityNodeCursorPosition;
    }
    
    void AccessibilityManager::Initialize(const std::string &id, OH_NativeXComponent *nativeXComponent)
    {
        int32_t ret = OH_NativeXComponent_GetNativeAccessibilityProvider(nativeXComponent, &provider);
        if (provider == nullptr) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "get provider is null");
            return;
        }
        // 2. Register callbacks.
        ret = OH_ArkUI_AccessibilityProviderRegisterCallbackWithInstance(id.c_str(), provider,
            &accessibilityProviderCallbacksWithInstance_);
        if (ret != 0) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                         "InterfaceDesignTest OH_ArkUI_AccessibilityProviderRegisterCallback failed");
            return;
        }
        g_provider = provider;
    }
    
    // ···
    
  3. Third-party frameworks must implement the following callbacks:

    int32_t AccessibilityManager::FindAccessibilityNodeInfosById(const char* instanceId, int64_t elementId,
        ArkUI_AccessibilitySearchMode mode, int32_t requestId, ArkUI_AccessibilityElementInfoList *elementList)
    {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "FindAccessibilityNodeInfosById start,instanceId %{public}s elementId: %{public}ld, "
                     "requestId: %{public}d, mode: %{public}d", instanceId,
                     elementId, requestId, static_cast<int32_t>(mode));
        if (elementList == nullptr) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                         "FindAccessibilityNodeInfosById elementList is null");
            return OH_NATIVEXCOMPONENT_RESULT_FAILED;
        }
        int ret = 0;
        const int parentOfRoot = -2100000;
        if (elementId == -1) {
            elementId = 0;
        }
        
        if (mode == ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_RECURSIVE_CHILDREN) {
            // Third-party frameworks must implement custom search logic in this method and return accessibility node information to the accessibility service. The following implementation serves as a reference example only.
            // Special constant defined for ArkUI framework compatibility. Root node parent ID must be set to this specific value.
            auto rootNode = OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
            if (!rootNode) {
                return OH_NATIVEXCOMPONENT_RESULT_FAILED;
            }
            OH_ArkUI_AccessibilityElementInfoSetElementId(rootNode, 0);
            OH_ArkUI_AccessibilityElementInfoSetParentId(rootNode, parentOfRoot);
            FakeWidget::Instance().fillAccessibilityElement(rootNode);
    
            ArkUI_AccessibleRect rect;
            rect.leftTopX = NUMBER_ZERO;
            rect.leftTopY = NUMBER_ZERO;
            rect.rightBottomX = NUMBER_THIRD;
            rect.rightBottomY = NUMBER_THIRD;
            ret = OH_ArkUI_AccessibilityElementInfoSetScreenRect(rootNode, &rect);
            OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(rootNode, "no");
            auto objects = FakeWidget::Instance().GetAllObjects(instanceId);
            int64_t childNodes[1024];
            for (int i = 0; i < objects.size(); i++) {
                int elementId = i + 1;
    
                childNodes[i] = elementId;
            }
            for (int i = 0; i < objects.size(); i++) {
                int elementId = i + 1;
                childNodes[i] = elementId;
                auto child = OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
                OH_ArkUI_AccessibilityElementInfoSetElementId(child, elementId);
                OH_ArkUI_AccessibilityElementInfoSetParentId(child, 0);
                OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(child, "yes");
                objects[i]->fillAccessibilityElement(child);
    
                ArkUI_AccessibleRect rect;
                rect.leftTopX = i * NUMBER_FIRST;
                rect.leftTopY = NUMBER_FIRST;
                rect.rightBottomX = i * NUMBER_FIRST + NUMBER_FIRST;
                rect.rightBottomY = NUMBER_SECOND;
                OH_ArkUI_AccessibilityElementInfoSetScreenRect(child, &rect);
                if (objects[i]->ObjectType() == "FakeSlider") {
                    auto rangeInfo = objects[i]->GetRangeInfo();
                    OH_ArkUI_AccessibilityElementInfoSetRangeInfo(child, &rangeInfo);
                }
                if (objects[i]->ObjectType() == "FakeList") {
                    auto gridInfo = objects[i]->GetGridInfo();
                    OH_ArkUI_AccessibilityElementInfoSetGridInfo(child, &gridInfo);
                }
                if (objects[i]->ObjectType() == "FakeSwiper") {
                    auto gridItemInfo = objects[i]->GetGridItemInfo();
                    OH_ArkUI_AccessibilityElementInfoSetGridItemInfo(child, &gridItemInfo);
                }
            }
    
            ret = OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(rootNode, objects.size(), childNodes);
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                         "FindAccessibilityNodeInfosById child count: %{public}ld %{public}d",
                         objects.size(), ret);
        } else if (mode == ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_CURRENT) {
            auto &widget = FakeWidget::Instance();
            AccessibleObject *obj = nullptr;
            if (elementId == 0) {
                obj = &widget;
            } else {
                obj = widget.GetChild(elementId);
            }
            if (!obj) {
                return OH_NATIVEXCOMPONENT_RESULT_FAILED;
            }
            auto node = OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
            OH_ArkUI_AccessibilityElementInfoSetElementId(node, elementId);
            OH_ArkUI_AccessibilityElementInfoSetParentId(node, elementId == 0 ? parentOfRoot : 0);
            OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(node, elementId == 0 ?  "no" : "yes");
            obj->fillAccessibilityElement(node);
            ArkUI_AccessibleRect rect;
            if (elementId == 0) {
                rect.leftTopX = NUMBER_ZERO;
                rect.leftTopY = NUMBER_ZERO;
                rect.rightBottomX = NUMBER_THIRD;
                rect.rightBottomY = NUMBER_THIRD;
            } else {
                int i = elementId - 1;
                rect.leftTopX = i * NUMBER_FIRST;
                rect.leftTopY = NUMBER_FIRST;
                rect.rightBottomX = i * NUMBER_FIRST + NUMBER_FIRST;
                rect.rightBottomY = NUMBER_SECOND;
            }
    
            OH_ArkUI_AccessibilityElementInfoSetScreenRect(node, &rect);
            if (elementId == 0) {
                auto objects = FakeWidget::Instance().GetAllObjects(instanceId);
                int64_t childNodes[1024];
    
                for (int i = 0; i < objects.size(); i++) {
                    int elementId = i + 1;
    
                    childNodes[i] = elementId;
                    auto child = OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList);
                    OH_ArkUI_AccessibilityElementInfoSetElementId(child, elementId);
                    OH_ArkUI_AccessibilityElementInfoSetParentId(child, 0);
    
                    objects[i]->fillAccessibilityElement(child);
    
                    ArkUI_AccessibleRect rect;
                    rect.leftTopX = i * NUMBER_FIRST;
                    rect.leftTopY = NUMBER_ZERO;
                    rect.rightBottomX = i * NUMBER_FIRST + NUMBER_FIRST;
                    rect.rightBottomY = NUMBER_SECOND;
                    OH_ArkUI_AccessibilityElementInfoSetScreenRect(child, &rect);
                }
                ret = OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(node, objects.size(), childNodes);
                OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                             "FindAccessibilityNodeInfosById child2 count: %{public}ld", objects.size());
            }
        }
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "FindAccessibilityNodeInfosById end");
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    int32_t AccessibilityManager::FindNextFocusAccessibilityNode(const char* instanceId, int64_t elementId,
        ArkUI_AccessibilityFocusMoveDirection direction, int32_t requestId,
        ArkUI_AccessibilityElementInfo *elementInfo)
    {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "FindNextFocusAccessibilityNode instanceId %{public}s "
                     "elementId: %{public}ld, requestId: %{public}d, direction: %{public}d",
                     instanceId, elementId, requestId, static_cast<int32_t>(direction));
        auto objects = FakeWidget::Instance().GetAllObjects(instanceId);
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "objects.size() %{public}d", objects.size());
        // object.size does not contain the root node.
        if ((elementId < 0) || (elementId > objects.size())) {
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "elementId invalid");
            return OH_NATIVEXCOMPONENT_RESULT_FAILED;
        }
        int64_t nextElementId = -1;
        if (direction == ARKUI_ACCESSIBILITY_NATIVE_DIRECTION_FORWARD) {
            nextElementId = elementId + 1;
        } else {
            nextElementId = elementId - 1;
        }
        
        // Screen reader constraint: If it is the root node and navigating backward, return to the last node.
        if ((nextElementId == -1) && (direction == ARKUI_ACCESSIBILITY_NATIVE_DIRECTION_BACKWARD)) {
            nextElementId = objects.size();
        }
        
        if (nextElementId >  objects.size()) {
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "nextElementId invalid");
            return OH_NATIVEXCOMPONENT_RESULT_FAILED;
        }
        
        if (nextElementId <=  0) {
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "nextElementId less than zero");
            return OH_NATIVEXCOMPONENT_RESULT_FAILED;
        }
        OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfo, nextElementId);
        OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, 0);
        // The ID is 1 greater than the object index.
        objects[nextElementId - 1]->fillAccessibilityElement(elementInfo);
        ArkUI_AccessibleRect rect;
        rect.leftTopX = nextElementId * NUMBER_FIRST;
        rect.leftTopY = NUMBER_ZERO;
        rect.rightBottomX = nextElementId * NUMBER_FIRST + NUMBER_FIRST;
        rect.rightBottomY = NUMBER_SECOND;
        OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfo, &rect);
        auto eventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
        OH_ArkUI_AccessibilityEventSetRequestFocusId(eventInfo, requestId);
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "%{public}ld", nextElementId);
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    int32_t AccessibilityManager::FindAccessibilityNodeInfosByText(const char* instanceId, int64_t elementId,
        const char *text, int32_t requestId, ArkUI_AccessibilityElementInfoList *elementList)
    {
        // Third-party frameworks need to implement the logic for querying accessibility nodes based on text content.
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "FindAccessibilityNodeInfosByText start,instanceId %{public}s elementId: %{public}ld, "
                     "requestId: %{public}d, text: %{public}s.", instanceId,
                     elementId, requestId, text);
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    int32_t AccessibilityManager::FindFocusedAccessibilityNode(const char* instanceId, int64_t elementId,
        ArkUI_AccessibilityFocusType focusType, int32_t requestId, ArkUI_AccessibilityElementInfo *elementInfo)
    {
        // Third-party frameworks need to implement the logic for obtaining the focus element information based on a specified node.
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "FindFocusedAccessibilityNode start instanceId %{public}s, "
                     "elementId: %{public}ld, requestId: %{public}d, focusType: %{public}d",
                     instanceId, elementId, requestId, static_cast<int32_t>(focusType));
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    void FillEvent(ArkUI_AccessibilityEventInfo *eventInfo, ArkUI_AccessibilityElementInfo *elementInfo,
                   ArkUI_AccessibilityEventType eventType, std::string announcedText)
    {
        if (eventInfo == nullptr) {
            return;
        }
        if (elementInfo == nullptr) {
            return;
        }
        OH_ArkUI_AccessibilityEventSetEventType(eventInfo, eventType);
    
        OH_ArkUI_AccessibilityEventSetElementInfo(eventInfo, elementInfo);
        
        if (eventType == ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ANNOUNCE_FOR_ACCESSIBILITY && announcedText.size() > 0) {
            OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility(eventInfo, announcedText.data());
        }
    }
    
    // ···
    
    void AccessibilityManager::SendAccessibilityAsyncEvent(ArkUI_AccessibilityElementInfo *elementInfo,
                                                           ArkUI_AccessibilityEventType eventType,
                                                           std::string announcedText)
    {
        auto eventInfo = OH_ArkUI_CreateAccessibilityEventInfo();
        // 1. Enter the event content.
        FillEvent(eventInfo, elementInfo, eventType, announcedText);
        // 2. Callback
        auto callback = [](int32_t errorCode) {
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT, "result: %{public}d", errorCode);
        };
        // 3. Call the API to send the event to the OpenHarmony side.
        OH_ArkUI_SendAccessibilityAsyncEvent(g_provider, eventInfo, callback);
    }
    // ···
    
    int32_t AccessibilityManager::ExecuteAccessibilityAction(const char* instanceId, int64_t elementId,
        ArkUI_Accessibility_ActionType action, ArkUI_AccessibilityActionArguments *actionArguments, int32_t requestId)
    {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "ExecuteAccessibilityAction instanceId %{public}s elementId: %{public}ld, "
                     "action: %{public}d, requestId: %{public}d",
                     instanceId, elementId, action, requestId);
        auto object = FakeWidget::Instance().GetChild(elementId);
        if (!object) {
            return 0;
        }
        auto announcedText = object->GetAnnouncedForAccessibility();
        auto element = OH_ArkUI_CreateAccessibilityElementInfo();
        OH_ArkUI_AccessibilityElementInfoSetElementId(element, elementId);
        const char *actionKey = "some_key";
        char *actionValue = nullptr;
        OH_ArkUI_FindAccessibilityActionArgumentByKey(actionArguments, actionKey, &actionValue);
        switch (action) {
            case ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK:
                if (object) {
                    object->OnClick();
                    object->fillAccessibilityElement(element);
                }
                AccessibilityManager::SendAccessibilityAsyncEvent(element,
                    ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_CLICKED, announcedText);
                break;
            case ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS:
                if (object) {
                    object->SetFocus(true);
    
                    object->fillAccessibilityElement(element);
                }
                // Send the specified event to the accessibility service.
                AccessibilityManager::SendAccessibilityAsyncEvent(element,
                    ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUSED,
                    announcedText);
                break;
            case ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS:
                if (object) {
                    object->SetFocus(false);
                    object->fillAccessibilityElement(element);
                }
                AccessibilityManager::SendAccessibilityAsyncEvent(
                    element, ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUS_CLEARED,
                    announcedText);
                break;
            default:
                // Handle unsupported action types.
                break;
        }
        OH_ArkUI_DestoryAccessibilityElementInfo(element);
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    int32_t AccessibilityManager::ClearFocusedFocusAccessibilityNode(const char* instanceId)
    {
        // Third-party frameworks need to implement the logic for clearing the currently focused node.
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "ClearFocusedFocusAccessibilityNode, instanceId %{public}s", instanceId);
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
    int32_t AccessibilityManager::GetAccessibilityNodeCursorPosition(const char* instanceId, int64_t elementId,
        int32_t requestId, int32_t *index)
    {
        // Third-party frameworks need to implement the logic for obtaining the cursor position within the current text component.
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, LOG_PRINT_TEXT,
                     "GetAccessibilityNodeCursorPosition, instanceId %{public}s "
                     "elementId: %{public}ld, requestId: %{public}d, index: %{public}d",
                     instanceId, elementId, requestId, index);
        return OH_NATIVEXCOMPONENT_RESULT_SUCCESS;
    }
    
  4. After successful integration, enable accessibility features in system settings.