diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 5972d4aee99239553df92eb69c069d6c293b26ac..07afbfd75d4fd7ae32d0bdbcc42674cb4c8afd32 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -6,13 +6,13 @@ #include +#include "flutter/fml/trace_event.h" #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" #include "flutter/lib/ui/window/platform_message_response_dart_port.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" -#include "flutter/fml/trace_event.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_library_natives.h" @@ -145,9 +145,6 @@ void PlatformConfiguration::DispatchPlatformMessage( std::shared_ptr dart_state = dispatch_platform_message_.dart_state().lock(); - FML_DLOG(INFO) - << "DispatchPlatformMessage channel: " - << message->channel(); if (!dart_state) { FML_DLOG(WARNING) << "Dropping platform message for lack of DartState on channel: " @@ -181,9 +178,7 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id, fml::MallocMapping args) { std::shared_ptr dart_state = dispatch_semantics_action_.dart_state().lock(); - FML_DLOG(INFO) - << "DispatchSemanticsAction : " - << id ; + if (!dart_state) { return; } @@ -195,7 +190,6 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id, if (Dart_IsError(args_handle)) { return; } - tonic::CheckAndHandleError(tonic::DartInvoke( dispatch_semantics_action_.Get(), {tonic::ToDart(id), tonic::ToDart(static_cast(action)), @@ -361,7 +355,6 @@ Dart_Handle PlatformConfigurationNativeApi::SendPortPlatformMessage( void PlatformConfigurationNativeApi::RespondToPlatformMessage( int response_id, const tonic::DartByteData& data) { - FML_DLOG(INFO)<<"PlatformConfigurationNativeApi::RespondToPlatformMessage:" << response_id; if (Dart_IsNull(data.dart_handle())) { UIDartState::Current() ->platform_configuration() diff --git a/shell/platform/ohos/BUILD.gn b/shell/platform/ohos/BUILD.gn index 689f5015c2a163805cc04cd776451815973c7ac3..68bd4bca43f047d8e0480864a847a748f748446e 100644 --- a/shell/platform/ohos/BUILD.gn +++ b/shell/platform/ohos/BUILD.gn @@ -84,6 +84,10 @@ source_set("flutter_ohos_sources") { "ohos_surface_gl_skia.h", "types.h", "ohos_logging.h", + "platform_view_ohos_delegate.h", + "./accessibility/ohos_accessibility_bridge.h", + "./accessibility/ohos_accessibility_features.h", + "./accessibility/ohos_accessibility_manager.h", ] #configs += [ "//flutter/shell/platform/ohos/config:gtk" ] @@ -114,6 +118,10 @@ source_set("flutter_ohos_sources") { "ohos_image_generator.cpp", "ohos_external_texture_gl.cpp", "./surface/ohos_snapshot_surface_producer.cpp", + "platform_view_ohos_delegate.cpp", + "./accessibility/ohos_accessibility_bridge.cpp", + "./accessibility/ohos_accessibility_features.cpp", + "./accessibility/ohos_accessibility_manager.cpp", ] # Set flag to stop headers being directly included (library users should not do this) diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a8b081d9cee93d8ccbe23ab28690e1d19f0ed6a --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp @@ -0,0 +1,1965 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 "ohos_accessibility_bridge.h" +#include +#include +#include "flutter/fml/logging.h" +#include "flutter/shell/common/platform_view.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/ohos/ohos_shell_holder.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkScalar.h" + +namespace flutter { + +OhosAccessibilityBridge OhosAccessibilityBridge::bridgeInstance; + +OhosAccessibilityBridge::OhosAccessibilityBridge() {}; +OhosAccessibilityBridge::~OhosAccessibilityBridge() {}; + +OhosAccessibilityBridge* OhosAccessibilityBridge::GetInstance() +{ + return &OhosAccessibilityBridge::bridgeInstance; +} + +/** + * 当页面状态更新事件,在页面转换、切换、调整大小时发送页面状态更新事件 + */ +void OhosAccessibilityBridge::PageStateUpdate(int64_t elementId) +{ + ArkUI_AccessibilityEventInfo* pageUpdateEventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); + + ArkUI_AccessibilityElementInfo* _elementInfo = + OH_ArkUI_CreateAccessibilityElementInfo(); + + OH_ArkUI_AccessibilityEventSetEventType( + pageUpdateEventInfo, + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUS_CLEARED); + FlutterNodeToElementInfoById(_elementInfo, elementId); + OH_ArkUI_AccessibilityEventSetElementInfo(pageUpdateEventInfo, _elementInfo); + + auto callback = [](int32_t errorCode) { + FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode; + }; + + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "PageStateUpdate ->" + "AccessibilityProvider = nullptr"; + return; + } + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, pageUpdateEventInfo, + callback); + + OH_ArkUI_DestoryAccessibilityEventInfo(pageUpdateEventInfo); + OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo); + + FML_DLOG(INFO) << "PageStateUpdate is end"; +} + +/** + * 特定节点的焦点请求 (当页面更新时自动请求id=0节点获焦) + */ +void OhosAccessibilityBridge::RequestFocusWhenPageUpdate() +{ + ArkUI_AccessibilityEventInfo* reqFocusEventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); + ArkUI_AccessibilityElementInfo* elementInfo = + OH_ArkUI_CreateAccessibilityElementInfo(); + + OH_ArkUI_AccessibilityEventSetEventType( + reqFocusEventInfo, + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_REQUEST_ACCESSIBILITY_FOCUS); + + int32_t requestFocusId = 0; + OH_ArkUI_AccessibilityEventSetRequestFocusId(reqFocusEventInfo, + requestFocusId); + + OH_ArkUI_AccessibilityEventSetElementInfo(reqFocusEventInfo, elementInfo); + + auto callback = [](int32_t errorCode) { + FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode; + }; + + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "PageStateUpdate ->" + "AccessibilityProvider = nullptr"; + return; + } + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, reqFocusEventInfo, callback); + + OH_ArkUI_DestoryAccessibilityEventInfo(reqFocusEventInfo); + OH_ArkUI_DestoryAccessibilityElementInfo(elementInfo); +} + +/** + * 从dart侧传递到c++侧的flutter无障碍语义树节点更新过程 + */ +void OhosAccessibilityBridge::updateSemantics( + flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions) +{ + FML_DLOG(INFO) << ("OhosAccessibilityBridge::updateSemantics is called"); + + // 当flutter页面更新时,自动请求id=0节点组件获焦(滑动组件除外) + if (IS_FLUTTER_NAVIGATE) { + RequestFocusWhenPageUpdate(); + IS_FLUTTER_NAVIGATE = false; + } + + /** 获取并分析每个语义节点的更新属性 */ + for (auto& item : update) { + // 获取当前更新的节点node + const flutter::SemanticsNode& node = item.second; + + // set struct SemanticsNodeExtent + auto nodeEx = SetAndGetSemanticsNodeExtent(node); + + // print semantics node and flags info for debugging + GetSemanticsNodeDebugInfo(node); + GetSemanticsFlagsDebugInfo(node); + + /** + * 构建flutter无障碍语义节点树 + * NOTE: 若使用flutterSemanticsTree_.insert({node.id, node})方式 + * 来添加新增的语义节点会导致已有key值自动忽略,不会更新原有key对应的value + */ + flutterSemanticsTree_[node.id] = node; + + // 若当前更新节点是隐藏的,则跳过 + if (node.HasFlag(FLAGS_::kIsHidden)) { + continue; + } + // 判断flutter节点是否获焦 + if (IsNodeFocusable(node)) { + FML_DLOG(INFO) << "UpdateSemantics -> flutterNode is focusable, node.id=" + << node.id; + } + + // 获取当前flutter节点的全部子节点数量,并构建父子节点id映射关系 + int32_t childNodeCount = node.childrenInTraversalOrder.size(); + for (int32_t i = 0; i < childNodeCount; i++) { + parentChildIdVec.emplace_back( + std::make_pair(node.id, node.childrenInTraversalOrder[i])); + FML_DLOG(INFO) << "UpdateSemantics parentChildIdMap -> (" << node.id + << ", " << node.childrenInTraversalOrder[i] << ")"; + } + + // 当滑动节点产生滑动,并执行滑动处理 + if (HasScrolled(node)) { + ArkUI_AccessibilityElementInfo* _elementInfo = + OH_ArkUI_CreateAccessibilityElementInfo(); + + FlutterNodeToElementInfoById(_elementInfo, static_cast(node.id)); + FlutterScrollExecution(node, _elementInfo); + + OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo); + } + + // 判断是否触发liveRegion活动区,当前节点是否活跃(暂不影响正常功能) + if (node.HasFlag(FLAGS_::kIsLiveRegion)) { + FML_DLOG(INFO) + << "UpdateSemantics -> flutterNode is kIsLiveRegion, node.id=" + << node.id; + } + } + + // 遍历更新的actions,并将所有的actions的id添加进actionMap + for (const auto& item : actions) { + const flutter::CustomAccessibilityAction action = item.second; + GetCustomActionDebugInfo(action); + actions_mp_[action.id] = action; + } + + // 打印flutter语义树的不同节点的属性信息 + for (const auto& item : flutterSemanticsTree_) { + FML_DLOG(INFO) << "flutterSemanticsTree_ -> {" << item.first << ", " + << item.second.id << "}"; + } + for (const auto& item : parentChildIdVec) { + FML_DLOG(INFO) << "parentChildIdVec -> (" << item.first << ", " + << item.second << ")"; + } + + FML_DLOG(INFO) << "=== UpdateSemantics is end ==="; +} + +/** + * flutter可滑动组件的滑动逻辑处理实现 + */ +void OhosAccessibilityBridge::FlutterScrollExecution( + flutter::SemanticsNode node, + ArkUI_AccessibilityElementInfo* elementInfoFromList) +{ + double nodePosition = node.scrollPosition; + double nodeScrollExtentMax = node.scrollExtentMax; + double nodeScrollExtentMin = node.scrollExtentMin; + double infinity = std::numeric_limits::infinity(); + + // 设置flutter可滑动的最大范围值 + if (nodeScrollExtentMax == infinity) { + nodeScrollExtentMax = SCROLL_EXTENT_FOR_INFINITY; + if (nodePosition > SCROLL_POSITION_CAP_FOR_INFINITY) { + nodePosition = SCROLL_POSITION_CAP_FOR_INFINITY; + } + } + if (nodeScrollExtentMin == infinity) { + nodeScrollExtentMax += SCROLL_EXTENT_FOR_INFINITY; + if (nodePosition < -SCROLL_POSITION_CAP_FOR_INFINITY) { + nodePosition = -SCROLL_POSITION_CAP_FOR_INFINITY; + } + nodePosition += SCROLL_EXTENT_FOR_INFINITY; + } else { + nodeScrollExtentMax -= node.scrollExtentMin; + nodePosition -= node.scrollExtentMin; + } + + if (node.HasAction(ACTIONS_::kScrollUp) || + node.HasAction(ACTIONS_::kScrollDown)) { + } else if (node.HasAction(ACTIONS_::kScrollLeft) || + node.HasAction(ACTIONS_::kScrollRight)) { + } + + // 当可滑动组件存在滑动子节点 + if (node.scrollChildren > 0) { + // 配置当前滑动组件的子节点总数 + int32_t itemCount = node.scrollChildren; + OH_ArkUI_AccessibilityElementInfoSetItemCount(elementInfoFromList, + itemCount); + // 设置当前页面可见的起始滑动index + int32_t startItemIndex = node.scrollIndex; + OH_ArkUI_AccessibilityElementInfoSetStartItemIndex(elementInfoFromList, + startItemIndex); + + // 计算当前滑动位置页面的可见子滑动节点数量 + int visibleChildren = 0; + // handle hidden children at the beginning and end of the list. + for (const auto& childId : node.childrenInHitTestOrder) { + auto childNode = getOrCreateFlutterSemanticsNode(childId); + if (!childNode.HasFlag(FLAGS_::kIsHidden)) { + visibleChildren += 1; + } + } + // 当可见滑动子节点数量超过滑动组件总子节点数量 + if (node.scrollIndex + visibleChildren > node.scrollChildren) { + FML_DLOG(ERROR) + << "FlutterScrollExecution -> Scroll index is out of bounds"; + } + // 当滑动击中子节点数量为0 + if (!node.childrenInHitTestOrder.size()) { + FML_DLOG(ERROR) << "FlutterScrollExecution -> Had scrollChildren but no " + "childrenInHitTestOrder"; + } + + // 设置当前页面可见的末尾滑动index + int32_t endItemIndex = node.scrollIndex + visibleChildren - 1; + OH_ArkUI_AccessibilityElementInfoSetEndItemIndex(elementInfoFromList, + endItemIndex); + } +} + +/** + * extent common struct SemanticsNode to + * derived struct SemanticsNodeExtent + */ +SemanticsNodeExtent OhosAccessibilityBridge::SetAndGetSemanticsNodeExtent( + flutter::SemanticsNode node) +{ + SemanticsNodeExtent nodeEx = SemanticsNodeExtent(); + nodeEx.id = std::move(node.id); + nodeEx.flags = std::move(node.flags); + nodeEx.actions = std::move(node.actions); + nodeEx.maxValueLength = std::move(node.maxValueLength); + nodeEx.currentValueLength = std::move(node.currentValueLength); + nodeEx.textSelectionBase = std::move(node.textSelectionBase); + nodeEx.textSelectionExtent = std::move(node.textSelectionExtent); + nodeEx.platformViewId = std::move(node.platformViewId); + nodeEx.scrollChildren = std::move(node.scrollChildren); + nodeEx.scrollIndex = std::move(node.scrollIndex); + nodeEx.scrollPosition = std::move(node.scrollPosition); + nodeEx.scrollExtentMax = std::move(node.scrollExtentMax); + nodeEx.scrollExtentMin = std::move(node.scrollExtentMin); + nodeEx.elevation = std::move(node.elevation); + nodeEx.thickness = std::move(node.thickness); + nodeEx.label = std::move(node.label); + nodeEx.labelAttributes = std::move(node.labelAttributes); + nodeEx.hint = std::move(node.hint); + nodeEx.hintAttributes = std::move(node.hintAttributes); + nodeEx.value = std::move(node.value); + nodeEx.valueAttributes = std::move(node.valueAttributes); + nodeEx.increasedValue = std::move(node.increasedValue); + nodeEx.increasedValueAttributes = std::move(node.increasedValueAttributes); + nodeEx.decreasedValue = std::move(node.decreasedValue); + nodeEx.decreasedValueAttributes = std::move(node.decreasedValueAttributes); + nodeEx.tooltip = std::move(node.tooltip); + nodeEx.textDirection = std::move(node.textDirection); + + nodeEx.rect = std::move(node.rect); + nodeEx.transform = std::move(node.transform); + nodeEx.childrenInTraversalOrder = std::move(node.childrenInTraversalOrder); + nodeEx.childrenInHitTestOrder = std::move(node.childrenInHitTestOrder); + nodeEx.customAccessibilityActions = + std::move(node.customAccessibilityActions); + + return nodeEx; +} + +/** + * 判断当前节点是否已经滑动 + */ +bool OhosAccessibilityBridge::HasScrolled( + const flutter::SemanticsNode& flutterNode) +{ + return flutterNode.scrollPosition != std::nan(""); +} +/** + * 判断是否可滑动 + */ +bool OhosAccessibilityBridge::IsNodeScrollable( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasAction(ACTIONS_::kScrollLeft) || + flutterNode.HasAction(ACTIONS_::kScrollRight) || + flutterNode.HasAction(ACTIONS_::kScrollUp) || + flutterNode.HasAction(ACTIONS_::kScrollDown); +} +/** + * 判断当前节点组件是否是滑动组件,如: listview, gridview等 + */ +bool OhosAccessibilityBridge::IsScrollableWidget( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kHasImplicitScrolling); +} + +/** + * 主动播报特定文本 + */ +void OhosAccessibilityBridge::announce(std::unique_ptr& message) +{ + // 创建并设置屏幕朗读事件 + ArkUI_AccessibilityEventInfo* announceEventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); + int32_t ret1 = OH_ArkUI_AccessibilityEventSetEventType( + announceEventInfo, + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ANNOUNCE_FOR_ACCESSIBILITY); + if (ret1 != 0) { + FML_DLOG(INFO) << "OhosAccessibilityBridge::announce " + "OH_ArkUI_AccessibilityEventSetEventType failed"; + return; + } + int32_t ret2 = OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility( + announceEventInfo, message.get()); + if (ret2 != 0) { + FML_DLOG(INFO) + << "OhosAccessibilityBridge::announce " + "OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility failed"; + return; + } + FML_DLOG(INFO) << ("OhosAccessibilityBridge::announce message: ") + << (message.get()); + + auto callback = [](int32_t errorCode) { + FML_DLOG(WARNING) << "announce callback-> errorCode =" << errorCode; + }; + + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "announce ->" + "AccessibilityProvider = nullptr"; + return; + } + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, announceEventInfo, callback); + + OH_ArkUI_DestoryAccessibilityEventInfo(announceEventInfo); + + return; +} + +/** + * 根据nodeid获取或创建flutter语义节点 + */ +flutter::SemanticsNode OhosAccessibilityBridge::getOrCreateFlutterSemanticsNode( + int32_t id) +{ + flutter::SemanticsNode node; + if (flutterSemanticsTree_.count(id) > 0) { + return flutterSemanticsTree_.at(id); + FML_DLOG(INFO) << "getOrCreateFlutterSemanticsNode get node.id=" << id; + } else { + FML_DLOG(ERROR) + << "getOrCreateFlutterSemanticsNode flutterSemanticsTree_ = null" << id; + return flutter::SemanticsNode{}; + } +} + +/** + * flutter的语义节点初始化配置给arkui创建的elementInfos + */ +void OhosAccessibilityBridge::FlutterTreeToArkuiTree( + ArkUI_AccessibilityElementInfoList* elementInfoList) +{ + if (flutterSemanticsTree_.size() == 0) { + FML_DLOG(ERROR) << "OhosAccessibilityBridge::FlutterTreeToArkuiTree " + "flutterSemanticsTree_.size() = 0"; + return; + } + // 将flutter语义节点树传递给arkui的无障碍elementinfo + for (const auto& item : flutterSemanticsTree_) { + flutter::SemanticsNode flutterNode = item.second; + + // 创建elementinfo,系统自动加入到elementinfolist + ArkUI_AccessibilityElementInfo* elementInfo = + OH_ArkUI_AddAndGetAccessibilityElementInfo(elementInfoList); + if (elementInfo == nullptr) { + FML_DLOG(INFO) << "OhosAccessibilityBridge::FlutterTreeToArkuiTree " + "elementInfo is null"; + return; + } + // 设置elementinfo的屏幕坐标范围 + int32_t left = static_cast(flutterNode.rect.fLeft); + int32_t top = static_cast(flutterNode.rect.fTop); + int32_t right = static_cast(flutterNode.rect.fRight); + int32_t bottom = static_cast(flutterNode.rect.fBottom); + ArkUI_AccessibleRect rect = {left, top, right, bottom}; + OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfo, &rect); + + // 设置elementinfo的action类型 + std::string widget_type = GetNodeComponentType(flutterNode); + FlutterSetElementInfoOperationActions(elementInfo, widget_type); + + // 设置elementid + OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfo, flutterNode.id); + + // 设置父节点id + int32_t parentId = GetParentId(flutterNode.id); + if (flutterNode.id == 0) { + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, -2100000); + FML_DLOG(INFO) << "FlutterTreeToArkuiTree parent.id= " << parentId; + } else { + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, parentId); + FML_DLOG(INFO) << "FlutterTreeToArkuiTree parent.id= " << parentId; + } + + // 设置孩子节点 + int32_t childCount = + static_cast(flutterNode.childrenInTraversalOrder.size()); + int64_t childNodeIds[childCount]; + for (int32_t i = 0; i < childCount; i++) { + childNodeIds[i] = + static_cast(flutterNode.childrenInTraversalOrder[i]); + FML_DLOG(INFO) << "FlutterTreeToArkuiTree flutterNode.id= " + << flutterNode.id << " childCount= " << childCount + << " childNodeId=" << childNodeIds[i]; + } + OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfo, childCount, + childNodeIds); + + // 配置常用属性,force to true for debugging + OH_ArkUI_AccessibilityElementInfoSetCheckable(elementInfo, true); + OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfo, true); + OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfo, true); + OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfo, true); + OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfo, true); + + // 设置组件类型 + std::string componentTypeName = GetNodeComponentType(flutterNode); + OH_ArkUI_AccessibilityElementInfoSetComponentType( + elementInfo, componentTypeName.c_str()); + + std::string contents = componentTypeName + "_content"; + OH_ArkUI_AccessibilityElementInfoSetContents(elementInfo, contents.c_str()); + + // 设置无障碍相关属性 + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText( + elementInfo, flutterNode.label.c_str()); + OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfo, "yes"); + OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfo, false); + } + FML_DLOG(INFO) << "FlutterTreeToArkuiTree is end"; +} + +/** + * 获取当前elementid的父节点id + */ +int32_t OhosAccessibilityBridge::GetParentId(int64_t elementId) +{ + int32_t childElementId = static_cast(elementId); + if (!parentChildIdVec.size()) { + FML_DLOG(INFO) + << "OhosAccessibilityBridge::GetParentId parentChildIdMap.size()=0"; + return -2100000; + } + for (const auto& item : parentChildIdVec) { + if (item.second == childElementId) { + return item.first; + } + } + return -2100000; +} + +/** + * 设置并获取xcomponet上渲染的组件的屏幕绝对坐标rect + */ +void OhosAccessibilityBridge::SetAbsoluteScreenRect(int32_t flutterNodeId, + float left, + float top, + float right, + float bottom) +{ + screenRectMap_[flutterNodeId] = + std::make_pair(std::make_pair(left, top), std::make_pair(right, bottom)); + FML_DLOG(INFO) << "SetAbsoluteScreenRect -> insert { " << flutterNodeId + << ", <" << left << ", " << top << ", " << right << ", " + << bottom << "> } is succeed"; +} + +std::pair, std::pair> +OhosAccessibilityBridge::GetAbsoluteScreenRect(int32_t flutterNodeId) +{ + if (!screenRectMap_.empty() && screenRectMap_.count(flutterNodeId) > 0) { + return screenRectMap_.at(flutterNodeId); + } else { + FML_DLOG(ERROR) << "GetAbsoluteScreenRect -> flutterNodeId=" + << flutterNodeId << " is not found !"; + return {}; + } +} + +/** + * flutter无障碍语义树的子节点相对坐标系转化为屏幕绝对坐标的映射算法 + * 目前暂未考虑旋转、透视场景,不影响屏幕朗读功能 + */ +void OhosAccessibilityBridge::ConvertChildRelativeRectToScreenRect( + flutter::SemanticsNode currNode) +{ + // 获取当前flutter节点的相对rect + auto currLeft = static_cast(currNode.rect.fLeft); + auto currTop = static_cast(currNode.rect.fTop); + auto currRight = static_cast(currNode.rect.fRight); + auto currBottom = static_cast(currNode.rect.fBottom); + + // 获取当前flutter节点的缩放、平移、透视等矩阵坐标转换 + SkMatrix transform = currNode.transform.asM33(); + auto _kMScaleX = transform.get(SkMatrix::kMScaleX); + auto _kMTransX = transform.get(SkMatrix::kMTransX); + auto _kMScaleY = transform.get(SkMatrix::kMScaleY); + auto _kMTransY = transform.get(SkMatrix::kMTransY); + /** 以下矩阵坐标变换参数(如:旋转/错切、透视)场景目前暂不考虑 + * NOTE: SkMatrix::kMSkewX, SkMatrix::kMSkewY, + * SkMatrix::kMPersp0, SkMatrix::kMPersp1, SkMatrix::kMPersp2 + */ + + // 获取当前flutter节点的父节点的相对rect + int32_t parentId = GetParentId(currNode.id); + auto parentNode = getOrCreateFlutterSemanticsNode(parentId); + auto parentRight = parentNode.rect.fRight; + auto parentBottom = parentNode.rect.fBottom; + + // 获取当前flutter节点的父节点的绝对坐标 + auto _rectPairs = GetAbsoluteScreenRect(parentNode.id); + auto realParentLeft = _rectPairs.first.first; + auto realParentTop = _rectPairs.first.second; + auto realParentRight = _rectPairs.second.first; + auto realParentBottom = _rectPairs.second.second; + + // 获取root节点的绝对坐标 + auto _rootRect = GetAbsoluteScreenRect(0); + auto rootRight = _rootRect.second.first; + auto rootBottom = _rootRect.second.second; + + // 真实缩放系数 + float realScaleFactor = realParentRight / parentRight * 1.0; + float newLeft, newTop, newRight, newBottom; + + if (_kMScaleX > 1 && _kMScaleY > 1) { + // 子节点相对父节点进行变化(缩放、 平移) + newLeft = currLeft + _kMTransX * _kMScaleX; + newTop = currTop + _kMTransY * _kMScaleY; + newRight = currRight * _kMScaleX; + newBottom = currBottom * _kMScaleY; + // 更新当前flutter节点currNode的相对坐标 -> 屏幕绝对坐标 + SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom); + } else { + // 若当前节点的相对坐标与父亲节点的相对坐标值相同,则直接继承坐标值 + if (currRight == parentRight && currBottom == parentBottom) { + newLeft = realParentLeft; + newTop = realParentTop; + newRight = realParentRight; + newBottom = realParentBottom; + } else { + /** + * 子节点的屏幕绝对坐标转换,包括offset偏移值计算、缩放系数变换 (初期版本) + * newLeft = (currLeft + _kMTransX) * realScaleFactor; + * newTop = (currTop + _kMTransY) * realScaleFactor; + * newRight = (currLeft + _kMTransX + currRight) * realScaleFactor; + * newBottom = (currTop + _kMTransY + currBottom) * realScaleFactor; + */ + // 子节点的屏幕绝对坐标转换,包括offset偏移值计算、缩放系数变换 + // (增强版本) + newLeft = (currLeft + _kMTransX) * realScaleFactor + realParentLeft; + newTop = (currTop + _kMTransY) * realScaleFactor + realParentTop; + newRight = + (currLeft + _kMTransX + currRight) * realScaleFactor + realParentLeft; + newBottom = + (currTop + _kMTransY + currBottom) * realScaleFactor + realParentTop; + } + + // 若子节点rect超过父节点则跳过显示(单个屏幕显示不下,滑动再重新显示) + if (newLeft < realParentLeft || newTop < realParentTop || + newRight > realParentRight || newBottom > realParentBottom || + newLeft >= newRight || newTop >= newBottom) { + FML_DLOG(ERROR) << "ConvertChildRelativeRectToScreenRect childRect is " + "bigger than parentRect -> { nodeId: " + << currNode.id << ", (" << newLeft << ", " << newTop + << ", " << newRight << ", " << newBottom << ")}"; + // 防止溢出屏幕坐标 + newTop = realParentTop - rootRight; + newBottom = realParentBottom - rootBottom; + SetAbsoluteScreenRect(currNode.id, newLeft, newTop, realParentRight, + newBottom); + } else { + SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom); + } + } + FML_DLOG(INFO) << "ConvertChildRelativeRectToScreenRect -> { nodeId: " + << currNode.id << ", (" << newLeft << ", " << newTop << ", " + << newRight << ", " << newBottom << ")}"; +} + +/** + * 实现对特定id的flutter节点到arkui的elementinfo节点转化 + */ +void OhosAccessibilityBridge::FlutterNodeToElementInfoById( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + int64_t elementId) +{ + if (elementInfoFromList == nullptr) { + FML_DLOG(INFO) << "OhosAccessibilityBridge::FlutterNodeToElementInfoById " + "elementInfoFromList is null"; + return; + } + + /** NOTE: when elementId == 0 || elementId == -1 */ + if (elementId == 0 || elementId == -1) { + // 获取flutter的root节点 + flutter::SemanticsNode flutterNode = + getOrCreateFlutterSemanticsNode(static_cast(0)); + + // 设置elementinfo的屏幕坐标范围 + int32_t left = static_cast(flutterNode.rect.fLeft); + int32_t top = static_cast(flutterNode.rect.fTop); + int32_t right = static_cast(flutterNode.rect.fRight); + int32_t bottom = static_cast(flutterNode.rect.fBottom); + ArkUI_AccessibleRect rect = {left, top, right, bottom}; + OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect); + // 设置root节点的屏幕绝对坐标rect + SetAbsoluteScreenRect(0, left, top, right, bottom); + + // 设置elementinfo的action类型 + std::string widget_type = "root"; + FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type); + + // 根据flutternode信息配置对应的elementinfo + OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, 0); + // NOTE: arkui无障碍子系统强制设置root的父节点id = -2100000 (严禁更改) + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, -2100000); + // 设置无障碍播报文本 + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText( + elementInfoFromList, flutterNode.label.empty() + ? flutterNode.hint.c_str() + : flutterNode.label.c_str()); + OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, + "yes"); + OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, + false); + + // 配置child节点信息 + int32_t childCount = + static_cast(flutterNode.childrenInTraversalOrder.size()); + int64_t childNodeIds[childCount]; + for (int32_t i = 0; i < childCount; i++) { + childNodeIds[i] = + static_cast(flutterNode.childrenInTraversalOrder[i]); + FML_DLOG(INFO) + << "FlutterNodeToElementInfoById -> elementid=0 childCount=" + << childCount << " childNodeIds=" << childNodeIds[i]; + } + OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, + childCount, childNodeIds); + + // 配置root节点常用属性 + OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true); + OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true); + OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true); + OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true); + OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, + "root"); + return; + } + + /** NOTE: when elementId >= 1 */ + FML_DLOG(INFO) << "FlutterNodeToElementInfoById elementId = " << elementId; + + // 根据flutter节点信息配置elementinfo无障碍属性 + FlutterSetElementInfoProperties(elementInfoFromList, elementId); + + FML_DLOG(INFO) + << "=== OhosAccessibilityBridge::FlutterNodeToElementInfoById is end ==="; +} + +/** + * 判断源字符串是否包含目标字符串 + */ +bool OhosAccessibilityBridge::Contains(const std::string source, + const std::string target) +{ + return source.find(target) != std::string::npos; +} + +/** + * 配置arkui节点的可操作动作类型 + */ +void OhosAccessibilityBridge::FlutterSetElementInfoOperationActions( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + std::string widget_type) +{ + if (widget_type == "textfield") { + // set elementinfo action types + int32_t actionTypeNum = 10; + ArkUI_AccessibleAction actions[actionTypeNum]; + + actions[0].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS; + actions[0].description = "获取焦点"; + + actions[1].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS; + actions[1].description = "清除焦点"; + + actions[2].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK; + actions[2].description = "点击操作"; + + actions[3].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK; + actions[3].description = "长按操作"; + + actions[4].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY; + actions[4].description = "文本复制"; + + actions[5].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE; + actions[5].description = "文本粘贴"; + + actions[6].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT; + actions[6].description = "文本剪切"; + + actions[7].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT; + actions[7].description = "文本选择"; + + actions[8].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT; + actions[8].description = "文本内容设置"; + + actions[9].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION; + actions[9].description = "光标位置设置"; + + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions); + + } else if (widget_type == "scrollable") { + // if node is a scrollable component + int32_t actionTypeNum = 5; + ArkUI_AccessibleAction actions[actionTypeNum]; + + actions[0].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS; + actions[0].description = "获取焦点"; + + actions[1].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS; + actions[1].description = "清除焦点"; + + actions[2].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK; + actions[2].description = "点击动作"; + + actions[3].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD; + actions[3].description = "向上滑动"; + + actions[4].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD; + actions[4].description = "向下滑动"; + + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions); + + } else { + // set common component action types + int32_t actionTypeNum = 3; + ArkUI_AccessibleAction actions[actionTypeNum]; + + actions[0].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS; + actions[0].description = "获取焦点"; + + actions[1].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS; + actions[1].description = "清除焦点"; + + actions[2].actionType = ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK; + actions[2].description = "点击动作"; + + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions); + } +} + +/** + * 根据flutter节点信息配置elementinfo无障碍属性 + */ +void OhosAccessibilityBridge::FlutterSetElementInfoProperties( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + int64_t elementId) +{ + flutter::SemanticsNode flutterNode = + getOrCreateFlutterSemanticsNode(static_cast(elementId)); + + // set elementinfo id + OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, + flutterNode.id); + // convert relative rect to absolute rect + ConvertChildRelativeRectToScreenRect(flutterNode); + auto rectPairs = GetAbsoluteScreenRect(flutterNode.id); + // set screen rect in xcomponent + int32_t left = rectPairs.first.first; + int32_t top = rectPairs.first.second; + int32_t right = rectPairs.second.first; + int32_t bottom = rectPairs.second.second; + ArkUI_AccessibleRect rect = {left, top, right, bottom}; + OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect); + FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> node.id= " + << flutterNode.id << " SceenRect = (" << left << ", " << top + << ", " << right << ", " << bottom << ")"; + + // 配置arkui的elementinfo可操作动作属性 + if (IsTextField(flutterNode)) { + // 若当前flutter节点为文本输入框组件 + std::string widget_type = "textfield"; + FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type); + } else if (IsScrollableWidget(flutterNode) || IsNodeScrollable(flutterNode)) { + // 若当前flutter节点为可滑动组件类型 + std::string widget_type = "scrollable"; + FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type); + } else { + // 若当前flutter节点为通用组件 + std::string widget_type = "common"; + FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type); + } + + // set current elementinfo parent id + int32_t parentId = GetParentId(elementId); + if (parentId < 0) { + FML_DLOG(ERROR) + << "FlutterNodeToElementInfoById GetParentId is null, assigned to " + << parentId; + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, parentId); + } else { + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, parentId); + FML_DLOG(INFO) << "FlutterNodeToElementInfoById GetParentId = " << parentId; + } + + // set accessibility text for announcing + std::string text = flutterNode.label; + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText(elementInfoFromList, + text.c_str()); + FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetAccessibilityText = " + << text; + + std::string hint = flutterNode.hint; + OH_ArkUI_AccessibilityElementInfoSetHintText(elementInfoFromList, + hint.c_str()); + + // set chidren elementinfo ids + int32_t childCount = + static_cast(flutterNode.childrenInTraversalOrder.size()); + int64_t childNodeIds[childCount]; + for (int32_t i = 0; i < childCount; i++) { + childNodeIds[i] = + static_cast(flutterNode.childrenInTraversalOrder[i]); + FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> elementid=" << elementId + << " childCount=" << childCount + << " childNodeIds=" << childNodeIds[i]; + } + OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, + childCount, childNodeIds); + + /** + * 根据当前flutter节点的SemanticsFlags特性,配置对应的elmentinfo属性 + */ + // 判断当前节点是否可点击 + if (IsNodeClickable(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetClickable -> true"; + } + // 判断当前节点是否可获焦点 + if (IsNodeFocusable(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetFocusable -> true"; + } + // 判断当前节点是否为密码输入框 + if (IsNodePassword(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetIsPassword(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetIsPassword -> true"; + } + // 判断当前节点是否具备checkable状态 (如:checkbox, radio button) + if (IsNodeCheckable(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetCheckable(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetCheckable -> true"; + } + // 判断当前节点(check box/radio button)是否checked/unchecked + if (IsNodeChecked(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetChecked(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetChecked -> true"; + } + // 判断当前节点组件是否可显示 + if (IsNodeVisible(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetVisible -> true"; + } + // 判断当前节点组件是否选中 + if (IsNodeSelected(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetSelected(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetSelected -> true"; + } + // 判断当前节点组件是否可滑动 + if (IsNodeScrollable(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetScrollable(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetScrollable -> true"; + } + // 判断当前节点组件是否可编辑(文本输入框) + if (IsTextField(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetEditable(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetEditable -> true"; + } + // 判断当前节点组件是否为滑动条 + if (IsSlider(flutterNode)) { + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetRangeInfo -> true"; + } + // 判断当前节点组件是否支持长按 + if (IsNodeHasLongPress(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetLongClickable(elementInfoFromList, + true); + FML_DLOG(INFO) + << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetLongClickable -> true"; + } + // 判断当前节点组件是否enabled + if (IsNodeEnabled(flutterNode)) { + OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true); + FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id + << " OH_ArkUI_AccessibilityElementInfoSetEnabled -> true"; + } + + // 获取当前节点的组件类型 + std::string componentTypeName = GetNodeComponentType(flutterNode); + FML_DLOG(INFO) << "FlutterNodeToElementInfoById componentTypeName = " + << componentTypeName; + // flutter节点对应elementinfo所属的组件类型(如:root, button,text等) + if (elementId == 0) { + OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, + "root"); + } else { + OH_ArkUI_AccessibilityElementInfoSetComponentType( + elementInfoFromList, componentTypeName.c_str()); + } + FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetComponentType: " + << componentTypeName; + + /** + * 无障碍重要性,用于控制某个组件是否可被无障碍辅助服务所识别。支持的值为(默认值:“auto”): + * “auto”:根据组件不同会转换为“yes”或者“no” + * “yes”:当前组件可被无障碍辅助服务所识别 + * “no”:当前组件不可被无障碍辅助服务所识别 + * “no-hide-descendants”:当前组件及其所有子组件不可被无障碍辅助服务所识别 + */ + OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, + "yes"); + // 无障碍组,设置为true时表示该组件及其所有子组件为一整个可以选中的组件,无障碍服务将不再关注其子组件内容。默认值:false + OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, + false); +} + +/** + * 判断当前节点是否为textfield文本框 + */ +bool OhosAccessibilityBridge::IsTextField(flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsTextField); +} +/** + * 判断当前节点是否为滑动条slider类型 + */ +bool OhosAccessibilityBridge::IsSlider(flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsSlider); +} +/** + * 判断当前flutter节点组件是否可点击 + */ +bool OhosAccessibilityBridge::IsNodeClickable( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasAction(ACTIONS_::kTap) || + flutterNode.HasFlag(FLAGS_::kHasCheckedState) || + flutterNode.HasFlag(FLAGS_::kIsButton) || + flutterNode.HasFlag(FLAGS_::kIsTextField) || + flutterNode.HasFlag(FLAGS_::kIsImage) || + flutterNode.HasFlag(FLAGS_::kIsLiveRegion) || + flutterNode.HasFlag(FLAGS_::kIsMultiline) || + flutterNode.HasFlag(FLAGS_::kIsLink) || + flutterNode.HasFlag(FLAGS_::kIsSlider) || + flutterNode.HasFlag(FLAGS_::kIsKeyboardKey) || + flutterNode.HasFlag(FLAGS_::kHasToggledState) || + flutterNode.HasFlag(FLAGS_::kHasImplicitScrolling); +} +/** + * 判断当前flutter节点组件是否可显示 + */ +bool OhosAccessibilityBridge::IsNodeVisible( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsHidden) ? false : true; +} +/** + * 判断当前flutter节点组件是否具备checkable属性 + */ +bool OhosAccessibilityBridge::IsNodeCheckable( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kHasCheckedState) || + flutterNode.HasFlag(FLAGS_::kHasToggledState); +} +/** + * 判断当前flutter节点组件是否checked/unchecked(checkbox、radio button) + */ +bool OhosAccessibilityBridge::IsNodeChecked( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsChecked) || + flutterNode.HasFlag(FLAGS_::kIsToggled); +} +/** + * 判断当前flutter节点组件是否选中 + */ +bool OhosAccessibilityBridge::IsNodeSelected( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsSelected); +} +/** + * 判断当前flutter节点组件是否为密码输入框 + */ +bool OhosAccessibilityBridge::IsNodePassword( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasFlag(FLAGS_::kIsTextField) && + flutterNode.HasFlag(FLAGS_::kIsObscured); +} +/** + * 判断当前flutter节点组件是否支持长按功能 + */ +bool OhosAccessibilityBridge::IsNodeHasLongPress( + flutter::SemanticsNode flutterNode) +{ + return flutterNode.HasAction(ACTIONS_::kLongPress); +} +/** + * 判断当前flutter节点是否enabled + */ +bool OhosAccessibilityBridge::IsNodeEnabled( + flutter::SemanticsNode flutterNode) +{ + return !flutterNode.HasFlag(FLAGS_::kHasEnabledState) || + flutterNode.HasFlag(FLAGS_::kIsEnabled); +} + +/** + * Called to obtain element information based on a specified node. + * NOTE:该arkui接口需要在系统无障碍服务开启时,才能触发调用 + */ +int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosById( + int64_t elementId, + ArkUI_AccessibilitySearchMode mode, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) +{ + FML_DLOG(INFO) + << "#### FindAccessibilityNodeInfosById input-params ####: elementId = " + << elementId << " mode=" << mode << " requestId=" << requestId + << " elementList= " << elementList; + + if (flutterSemanticsTree_.size() == 0) { + FML_DLOG(INFO) + << "FindAccessibilityNodeInfosById flutterSemanticsTree_ is null"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; + } + if (elementList == nullptr) { + FML_DLOG(INFO) << "FindAccessibilityNodeInfosById elementList is null"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; + } + + // 从elementinfolist中获取elementinfo + ArkUI_AccessibilityElementInfo* elementInfoFromList = + OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList); + if (elementInfoFromList == nullptr) { + FML_DLOG(INFO) + << "FindAccessibilityNodeInfosById elementInfoFromList is null"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; + } + + if (mode == ArkUI_AccessibilitySearchMode:: + ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_CURRENT) { + /** Search for current nodes. (mode = 0) */ + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + int64_t elementInfoCount = + static_cast(flutterSemanticsTree_.size()); + for (int64_t i = 1; i < elementInfoCount; i++) { + ArkUI_AccessibilityElementInfo* newElementInfo = + OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList); + FlutterNodeToElementInfoById(newElementInfo, i); + } + + } else if (mode == + ArkUI_AccessibilitySearchMode:: + ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_PREDECESSORS) { + /** Search for parent nodes. (mode = 1) */ + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + + } else if (mode == + ArkUI_AccessibilitySearchMode:: + ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_SIBLINGS) { + /** Search for sibling nodes. (mode = 2) */ + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + + } else if (mode == + ArkUI_AccessibilitySearchMode:: + ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_CHILDREN) { + /** Search for child nodes at the next level. (mode = 4) */ + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + + } else if ( + mode == + ArkUI_AccessibilitySearchMode:: + ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_RECURSIVE_CHILDREN) { + /** Search for all child nodes. (mode = 8) */ + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + int64_t elementInfoCount = + static_cast(flutterSemanticsTree_.size()); + for (int64_t i = 1; i < elementInfoCount; i++) { + ArkUI_AccessibilityElementInfo* newElementInfo = + OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList); + FlutterNodeToElementInfoById(newElementInfo, i); + } + + } else { + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + } + + FML_DLOG(INFO) << "--- FindAccessibilityNodeInfosById is end ---"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_SUCCESSFUL; +} + +/** + * Called to obtain element information based on a specified node and text + * content. + */ +int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosByText( + int64_t elementId, + const char* text, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) +{ + FML_DLOG(INFO) << "=== FindAccessibilityNodeInfosByText is end ==="; + return 0; +} +int32_t OhosAccessibilityBridge::FindFocusedAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusType focusType, + int32_t requestId, + ArkUI_AccessibilityElementInfo* elementinfo) +{ + FML_DLOG(INFO) << "=== FindFocusedAccessibilityNode is end ==="; + + return 0; +} +int32_t OhosAccessibilityBridge::FindNextFocusAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusMoveDirection direction, + int32_t requestId, + ArkUI_AccessibilityElementInfo* elementList) { + FML_DLOG(INFO) << "=== FindNextFocusAccessibilityNode is end ==="; + return 0; +} + +int32_t OhosAccessibilityBridge::ClearFocusedFocusAccessibilityNode() +{ + FML_DLOG(INFO) << "=== ClearFocusedFocusAccessibilityNode is end ==="; + return 0; +} +int32_t OhosAccessibilityBridge::GetAccessibilityNodeCursorPosition( + int64_t elementId, + int32_t requestId, + int32_t* index) +{ + FML_DLOG(INFO) << "=== GetAccessibilityNodeCursorPosition is end ==="; + return 0; +} + +/** + * 将arkui的action类型转化为flutter的action类型 + */ +flutter::SemanticsAction OhosAccessibilityBridge::ArkuiActionsToFlutterActions( + ArkUI_Accessibility_ActionType arkui_action) +{ + switch (arkui_action) { + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK: + return ACTIONS_::kTap; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK: + return ACTIONS_::kLongPress; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD: + return ACTIONS_::kScrollUp; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD: + return ACTIONS_::kScrollDown; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY: + return ACTIONS_::kCopy; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT: + return ACTIONS_::kCut; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS: + return ACTIONS_::kDidGainAccessibilityFocus; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS: + return ACTIONS_::kDidLoseAccessibilityFocus; + + // Text selection action, requiring the setting of selectTextBegin, + // TextEnd, and TextInForward parameters to select a text + // segment in the text box. */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT: + return ACTIONS_::kSetSelection; + + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT: + return ACTIONS_::kSetText; + + default: + // might not match to the valid action in arkui + return ACTIONS_::kCustomAction; + } +} + +/** + * covert arkui-specific touch action to flutter-specific action + * and dispatch it from C++ to Dart + */ +void OhosAccessibilityBridge::DispatchSemanticsAction( + int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args) +{ + auto ohos_shell_holder = + reinterpret_cast(nativeShellHolder_); + ohos_shell_holder->GetPlatformView()->PlatformView::DispatchSemanticsAction( + id, action, {}); + FML_DLOG(INFO) << "DispatchSemanticsAction -> shell_holder_id: " + << nativeShellHolder_ << " id: " << id + << " action: " << static_cast(action); +} + +/** + * 执行语义动作解析,当FindAccessibilityNodeInfosById找到相应的elementinfo时才会触发该回调函数 + */ +int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( + int64_t elementId, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments, + int32_t requestId) +{ + FML_DLOG(INFO) << "ExecuteAccessibilityAction input-params-> elementId=" + << elementId << " action=" << action + << " requestId=" << requestId + << " *actionArguments=" << actionArguments; + + if (actionArguments == nullptr) { + FML_DLOG(ERROR) << "OhosAccessibilityBridge::ExecuteAccessibilityAction " + "actionArguments = null"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; + } + + // 获取当前elementid对应的flutter语义节点 + auto flutterNode = + getOrCreateFlutterSemanticsNode(static_cast(elementId)); + + // 根据当前elementid和无障碍动作类型,发送无障碍事件 + switch (action) { + /** Response to a click. 16 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK: { + /** Click event, sent after the UI component responds. 1 */ + auto clickEventType = ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_CLICKED; + Flutter_SendAccessibilityAsyncEvent(elementId, clickEventType); + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: click(" << action + << ")" << " event: click(" << clickEventType << ")"; + + // 解析arkui的屏幕点击 -> flutter对应节点的屏幕点击 + auto flutterTapAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), flutterTapAction, + {}); + break; + } + + /** Response to a long click. 32 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK: { + /** Long click event, sent after the UI component responds. 2 */ + auto longClickEventType = ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_LONG_CLICKED; + Flutter_SendAccessibilityAsyncEvent(elementId, longClickEventType); + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: longclick(" + << action << ")" << " event: longclick(" + << longClickEventType << ")"; + + // 解析arkui的屏幕动作 -> flutter对应节点的屏幕动作 + auto flutterLongPressAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), + flutterLongPressAction, {}); + break; + } + + /** Accessibility focus acquisition. 64 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS: { + // 解析arkui的获焦 -> flutter对应节点的获焦 + auto flutterGainFocusAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), + flutterGainFocusAction, {}); + + /** Accessibility focus event, sent after the UI component responds. 32768 + */ + auto focusEventType = ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUSED; + Flutter_SendAccessibilityAsyncEvent(elementId, focusEventType); + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: focus(" << action + << ")" << " event: focus(" << focusEventType << ")"; + + if (flutterNode.HasAction(ACTIONS_::kIncrease) || + flutterNode.HasAction(ACTIONS_::kDecrease)) { + Flutter_SendAccessibilityAsyncEvent( + elementId, ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED); + } + + break; + } + + /** Accessibility focus clearance. 128 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS: { + // 解析arkui的失焦 -> flutter对应节点的失焦 + auto flutterLoseFocusAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), + flutterLoseFocusAction, {}); + + /** Accessibility focus cleared event, sent after the UI component + * responds. 65536 */ + auto clearFocusEventType = ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUS_CLEARED; + Flutter_SendAccessibilityAsyncEvent(elementId, clearFocusEventType); + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: clearfocus(" + << action << ")" << " event: clearfocus(" + << clearFocusEventType << ")"; + break; + } + + /** Forward scroll action. 256 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD: { + // flutter scroll forward with different situations + if (flutterNode.HasAction(ACTIONS_::kScrollUp)) { + auto flutterScrollUpAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), + flutterScrollUpAction, {}); + + } else if (flutterNode.HasAction(ACTIONS_::kScrollLeft)) { + DispatchSemanticsAction(static_cast(elementId), + ACTIONS_::kScrollLeft, {}); + + } else if (flutterNode.HasAction(ACTIONS_::kIncrease)) { + flutterNode.value = flutterNode.increasedValue; + flutterNode.valueAttributes = flutterNode.increasedValueAttributes; + + Flutter_SendAccessibilityAsyncEvent( + elementId, ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED); + DispatchSemanticsAction(static_cast(elementId), + ACTIONS_::kIncrease, {}); + + } else { + } + std::string currComponetType = GetNodeComponentType(flutterNode); + if (currComponetType == "ListView") { + /** Scrolled event, sent when a scrollable component experiences a + * scroll event. 4096 */ + ArkUI_AccessibilityEventType scrollEventType1 = + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SCROLLED; + Flutter_SendAccessibilityAsyncEvent(elementId, scrollEventType1); + FML_DLOG(INFO) + << "ExecuteAccessibilityAction -> action: scroll forward(" << action + << ")" << " event: scroll forward(" << scrollEventType1 << ")"; + } + break; + } + + /** Backward scroll action. 512 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD: { + // flutter scroll down with different situations + if (flutterNode.HasAction(ACTIONS_::kScrollDown)) { + auto flutterScrollDownAction = ArkuiActionsToFlutterActions(action); + DispatchSemanticsAction(static_cast(elementId), + flutterScrollDownAction, {}); + + } else if (flutterNode.HasAction(ACTIONS_::kScrollRight)) { + DispatchSemanticsAction(static_cast(elementId), + ACTIONS_::kScrollRight, {}); + + } else if (flutterNode.HasAction(ACTIONS_::kDecrease)) { + flutterNode.value = flutterNode.decreasedValue; + flutterNode.valueAttributes = flutterNode.decreasedValueAttributes; + + Flutter_SendAccessibilityAsyncEvent( + elementId, ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED); + DispatchSemanticsAction(static_cast(elementId), + ACTIONS_::kDecrease, {}); + + } else { + } + std::string currComponetType = GetNodeComponentType(flutterNode); + if (currComponetType == "ListView") { + /** Scrolled event, sent when a scrollable component experiences a + * scroll event. 4096 */ + ArkUI_AccessibilityEventType scrollBackwardEventType = + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SCROLLED; + Flutter_SendAccessibilityAsyncEvent(elementId, scrollBackwardEventType); + FML_DLOG(INFO) + << "ExecuteAccessibilityAction -> action: scroll backward(" + << action << ")" << " event: scroll backward(" + << scrollBackwardEventType << ")"; + } + break; + } + + /** Copy action for text content. 1024 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY: { + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: copy(" << action + << ")"; + DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kCopy, + {}); + break; + } + + /** Paste action for text content. 2048 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE: { + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: paste(" << action + << ")"; + DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kPaste, + {}); + break; + } + + /** Cut action for text content. 4096 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT: + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: cut(" << action + << ")"; + DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kCut, + {}); + break; + + /** Text selection action, requiring the setting of selectTextBegin, + * TextEnd, and TextInForward parameters to select a text + * segment in the text box. 8192 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT: { + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: select text(" + << action << ")"; + // 输入框文本选择操作 + PerformSelectText(flutterNode, action, actionArguments); + break; + } + + /** Text content setting action. 16384 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT: { + FML_DLOG(INFO) << "ExecuteAccessibilityAction -> action: set text(" + << action << ")"; + // 输入框设置文本 + PerformSetText(flutterNode, action, actionArguments); + break; + } + + /** Cursor position setting action. 1048576 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION: { + FML_DLOG(INFO) + << "ExecuteAccessibilityAction -> action: set cursor position(" + << action << ")"; + // 当前os接口不支持该功能,不影响正常屏幕朗读 + break; + } + + /** Invalid action. 0 */ + case ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_INVALID: { + /** Invalid event. 0 */ + ArkUI_AccessibilityEventType invalidEventType = + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_INVALID; + Flutter_SendAccessibilityAsyncEvent(elementId, invalidEventType); + FML_DLOG(ERROR) << "ExecuteAccessibilityAction -> action: invalid(" + << action << ")" << " event: innvalid(" + << invalidEventType << ")"; + break; + } + + default: { + /** custom semantics action */ + } + } + + FML_DLOG(INFO) << "--- ExecuteAccessibilityAction is end ---"; + return ARKUI_ACCESSIBILITY_NATIVE_RESULT_SUCCESSFUL; +} + +/** + * 自定义无障碍异步事件发送 + */ +void OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent( + int64_t elementId, + ArkUI_AccessibilityEventType eventType) +{ + // 1.创建eventInfo对象 + ArkUI_AccessibilityEventInfo* eventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); + if (eventInfo == nullptr) { + FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent " + "OH_ArkUI_CreateAccessibilityEventInfo eventInfo = null"; + return; + } + + // 2.创建的elementinfo并根据对应id的flutternode进行属性初始化 + ArkUI_AccessibilityElementInfo* _elementInfo = + OH_ArkUI_CreateAccessibilityElementInfo(); + FlutterNodeToElementInfoById(_elementInfo, elementId); + // 若为获焦事件,则设置当前elementinfo获焦 + if (eventType == + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ACCESSIBILITY_FOCUSED) { + OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused(_elementInfo, + true); + } + + // 3.设置发送事件,如配置获焦、失焦、点击、滑动事件 + OH_ArkUI_AccessibilityEventSetEventType(eventInfo, eventType); + + // 4.将eventinfo事件和当前elementinfo进行绑定 + OH_ArkUI_AccessibilityEventSetElementInfo(eventInfo, _elementInfo); + + // 5.调用接口发送到ohos侧 + auto callback = [](int32_t errorCode) { + FML_DLOG(INFO) + << "Flutter_SendAccessibilityAsyncEvent callback-> errorCode =" + << errorCode; + }; + + // 6.发送event到OH侧 + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent " + "AccessibilityProvider = nullptr"; + return; + } + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, eventInfo, callback); + + // 7.销毁新创建的elementinfo, eventinfo + OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo); + OH_ArkUI_DestoryAccessibilityEventInfo(eventInfo); + + FML_DLOG(INFO) + << "OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent is end"; + return; +} + +/** + * 判断当前语义节点是否获焦 + */ +bool OhosAccessibilityBridge::IsNodeFocusable( + const flutter::SemanticsNode& node) +{ + if (node.HasFlag(FLAGS_::kScopesRoute)) { + return false; + } + if (node.HasFlag(FLAGS_::kIsFocusable)) { + return true; + } + // Always consider platform views focusable. + if (node.IsPlatformViewNode()) { + return true; + } + // Always consider actionable nodes focusable. + if (node.actions != 0) { + return true; + } + if ((node.flags & FOCUSABLE_FLAGS) != 0) { + return true; + } + if ((node.actions & ~FOCUSABLE_FLAGS) != 0) { + return true; + } + // Consider text nodes focusable. + return !node.label.empty() || !node.value.empty() || !node.hint.empty(); +} + +void OhosAccessibilityBridge::PerformSetText( + flutter::SemanticsNode flutterNode, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments) {} + +void OhosAccessibilityBridge::PerformSelectText( + flutter::SemanticsNode flutterNode, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments) {} + +/** + * 获取当前flutter节点的组件类型,并映射为arkui组件 + */ +std::string OhosAccessibilityBridge::GetNodeComponentType( + const flutter::SemanticsNode& node) +{ + if (node.HasFlag(FLAGS_::kIsButton)) { + return "Button"; + } + + if (node.HasFlag(FLAGS_::kIsTextField)) { + // arkui没有textfield,这里直接透传或者传递textinput + return "TextField"; + } + + if (node.HasFlag(FLAGS_::kIsMultiline)) { + // arkui没有多行文本textfield,这里直接透传 + return "TextArea"; + } + + if (node.HasFlag(FLAGS_::kIsLink)) { + return "Link"; + } + + if (node.HasFlag(FLAGS_::kIsSlider) || node.HasAction(ACTIONS_::kIncrease) || + node.HasAction(ACTIONS_::kDecrease)) { + return "Slider"; + } + + if (node.HasFlag(FLAGS_::kIsHeader)) { + return "Header"; + } + + if (node.HasFlag(FLAGS_::kIsImage)) { + return "Image"; + } + + if (node.HasFlag(FLAGS_::kHasCheckedState)) { + if (node.HasFlag(FLAGS_::kIsInMutuallyExclusiveGroup)) { + // arkui没有RadioButton,这里透传为RadioButton + return "RadioButton"; + } else { + return "Checkbox"; + } + } + + if (node.HasFlag(FLAGS_::kHasToggledState)) { + // arkui没有ToggleSwitch,这里透传为Toggle + return "ToggleSwitch"; + } + + if ((!node.label.empty() || !node.tooltip.empty() || !node.hint.empty())) { + return "Text"; + } + + return "Widget" + std::to_string(node.id); +} + +// 获取根节点 +flutter::SemanticsNode OhosAccessibilityBridge::getFlutterRootSemanticsNode() +{ + if (!flutterSemanticsTree_.size()) { + FML_DLOG(ERROR) + << "getFlutterRootSemanticsNode -> flutterSemanticsTree_.size()=0"; + return flutter::SemanticsNode{}; + } + if (flutterSemanticsTree_.find(0) == flutterSemanticsTree_.end()) { + FML_DLOG(ERROR) << "getFlutterRootSemanticsNode -> flutterSemanticsTree_ " + "has no keys = 0"; + return flutter::SemanticsNode{}; + } + return flutterSemanticsTree_.at(0); +} + +void OhosAccessibilityBridge::AddRouteNodes( + std::vector edges, + flutter::SemanticsNode node) +{ + if (node.HasFlag(FLAGS_::kScopesRoute)) { + edges.emplace_back(node); + } + for (auto& childNodeId : node.childrenInTraversalOrder) { + auto childNode = getOrCreateFlutterSemanticsNode(childNodeId); + AddRouteNodes(edges, childNode); + } +} + +std::string OhosAccessibilityBridge::GetRouteName(flutter::SemanticsNode node) +{ + if (node.HasFlag(FLAGS_::kNamesRoute) && !node.label.empty()) { + return node.label; + } + for (auto& childNodeId : node.childrenInTraversalOrder) { + auto childNode = getOrCreateFlutterSemanticsNode(childNodeId); + std::string newName = GetRouteName(childNode); + if (!newName.empty()) { + return newName; + } + } + return ""; +} + +void OhosAccessibilityBridge::onWindowNameChange(flutter::SemanticsNode route) +{ + std::string routeName = GetRouteName(route); + if (routeName.empty()) { + routeName = " "; + } + Flutter_SendAccessibilityAsyncEvent( + static_cast(route.id), + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_PAGE_CONTENT_UPDATE); +} + +void OhosAccessibilityBridge::removeSemanticsNode( + flutter::SemanticsNode nodeToBeRemoved) +{ + if (!flutterSemanticsTree_.size()) { + FML_DLOG(ERROR) << "OhosAccessibilityBridge::removeSemanticsNode -> " + "flutterSemanticsTree_.szie()=0"; + return; + } + if (flutterSemanticsTree_.find(nodeToBeRemoved.id) == + flutterSemanticsTree_.end()) { + FML_DLOG(INFO) << "Attempted to remove a node that is not in the tree."; + } + int32_t nodeToBeRemovedParentId = GetParentId(nodeToBeRemoved.id); + for (auto it = parentChildIdVec.begin(); it != parentChildIdVec.end(); it++) { + if (it->first == nodeToBeRemovedParentId && + it->second == nodeToBeRemoved.id) { + parentChildIdVec.erase(it); + } + } + if (nodeToBeRemoved.platformViewId != -1) { + } +} + +/** + * when the system accessibility service is shut down, + * clear all the flutter semantics-relevant caches like maps, vectors + */ +void OhosAccessibilityBridge::ClearFlutterSemanticsCaches() +{ + flutterSemanticsTree_.clear(); + parentChildIdVec.clear(); + screenRectMap_.clear(); + actions_mp_.clear(); + flutterNavigationVec_.clear(); +} + +void OhosAccessibilityBridge::GetSemanticsNodeDebugInfo( + flutter::SemanticsNode node) +{ + FML_DLOG(INFO) << "-------------------SemanticsNode------------------"; + SkMatrix _transform = node.transform.asM33(); + FML_DLOG(INFO) << "node.id=" << node.id; + FML_DLOG(INFO) << "node.label=" << node.label; + FML_DLOG(INFO) << "node.tooltip=" << node.tooltip; + FML_DLOG(INFO) << "node.hint=" << node.hint; + FML_DLOG(INFO) << "node.flags=" << node.flags; + FML_DLOG(INFO) << "node.actions=" << node.actions; + FML_DLOG(INFO) << "node.rect= {" << node.rect.fLeft << ", " << node.rect.fTop + << ", " << node.rect.fRight << ", " << node.rect.fBottom + << "}"; + FML_DLOG(INFO) << "node.transform -> kMScaleX=" + << _transform.get(SkMatrix::kMScaleX); + FML_DLOG(INFO) << "node.transform -> kMSkewX=" + << _transform.get(SkMatrix::kMSkewX); + FML_DLOG(INFO) << "node.transform -> kMTransX=" + << _transform.get(SkMatrix::kMTransX); + FML_DLOG(INFO) << "node.transform -> kMSkewY=" + << _transform.get(SkMatrix::kMSkewY); + FML_DLOG(INFO) << "node.transform -> kMScaleY=" + << _transform.get(SkMatrix::kMScaleY); + FML_DLOG(INFO) << "node.transform -> kMTransY=" + << _transform.get(SkMatrix::kMTransY); + FML_DLOG(INFO) << "node.transform -> kMPersp0=" + << _transform.get(SkMatrix::kMPersp0); + FML_DLOG(INFO) << "node.transform -> kMPersp1=" + << _transform.get(SkMatrix::kMPersp1); + FML_DLOG(INFO) << "node.transform -> kMPersp2=" + << _transform.get(SkMatrix::kMPersp2); + FML_DLOG(INFO) << "node.maxValueLength=" << node.maxValueLength; + FML_DLOG(INFO) << "node.currentValueLength=" << node.currentValueLength; + FML_DLOG(INFO) << "node.textSelectionBase=" << node.textSelectionBase; + FML_DLOG(INFO) << "node.textSelectionExtent=" << node.textSelectionExtent; + FML_DLOG(INFO) << "node.textSelectionBase=" << node.textSelectionBase; + FML_DLOG(INFO) << "node.platformViewId=" << node.platformViewId; + FML_DLOG(INFO) << "node.scrollChildren=" << node.scrollChildren; + FML_DLOG(INFO) << "node.scrollIndex=" << node.scrollIndex; + FML_DLOG(INFO) << "node.scrollPosition=" << node.scrollPosition; + FML_DLOG(INFO) << "node.scrollIndex=" << node.scrollIndex; + FML_DLOG(INFO) << "node.scrollPosition=" << node.scrollPosition; + FML_DLOG(INFO) << "node.scrollExtentMax=" << node.scrollExtentMax; + FML_DLOG(INFO) << "node.scrollExtentMin=" << node.scrollExtentMin; + FML_DLOG(INFO) << "node.elevation=" << node.elevation; + FML_DLOG(INFO) << "node.thickness=" << node.thickness; + FML_DLOG(INFO) << "node.textDirection=" << node.textDirection; + FML_DLOG(INFO) << "node.childrenInTraversalOrder.size()=" + << node.childrenInTraversalOrder.size(); + for (uint32_t i = 0; i < node.childrenInTraversalOrder.size(); i++) { + FML_DLOG(INFO) << "node.childrenInTraversalOrder[" << i + << "]=" << node.childrenInTraversalOrder[i]; + } + FML_DLOG(INFO) << "node.childrenInHitTestOrder.size()=" + << node.childrenInHitTestOrder.size(); + for (uint32_t i = 0; i < node.childrenInHitTestOrder.size(); i++) { + FML_DLOG(INFO) << "node.childrenInHitTestOrder[" << i + << "]=" << node.childrenInHitTestOrder[i]; + } + FML_DLOG(INFO) << "node.customAccessibilityActions.size()=" + << node.customAccessibilityActions.size(); + for (uint32_t i = 0; i < node.customAccessibilityActions.size(); i++) { + FML_DLOG(INFO) << "node.customAccessibilityActions[" << i + << "]=" << node.customAccessibilityActions[i]; + } + FML_DLOG(INFO) << "------------------SemanticsNode-----------------"; +} + +void OhosAccessibilityBridge::GetSemanticsFlagsDebugInfo( + flutter::SemanticsNode node) +{ + FML_DLOG(INFO) << "----------------SemanticsFlags-------------------------"; + FML_DLOG(INFO) << "kHasCheckedState: " + << node.HasFlag(FLAGS_::kHasCheckedState); + FML_DLOG(INFO) << "kIsChecked:" << node.HasFlag(FLAGS_::kIsChecked); + FML_DLOG(INFO) << "kIsSelected:" << node.HasFlag(FLAGS_::kIsSelected); + FML_DLOG(INFO) << "kIsButton:" << node.HasFlag(FLAGS_::kIsButton); + FML_DLOG(INFO) << "kIsTextField:" << node.HasFlag(FLAGS_::kIsTextField); + FML_DLOG(INFO) << "kIsFocused:" << node.HasFlag(FLAGS_::kIsFocused); + FML_DLOG(INFO) << "kHasEnabledState:" + << node.HasFlag(FLAGS_::kHasEnabledState); + FML_DLOG(INFO) << "kIsEnabled:" << node.HasFlag(FLAGS_::kIsEnabled); + FML_DLOG(INFO) << "kIsInMutuallyExclusiveGroup:" + << node.HasFlag(FLAGS_::kIsInMutuallyExclusiveGroup); + FML_DLOG(INFO) << "kIsHeader:" << node.HasFlag(FLAGS_::kIsHeader); + FML_DLOG(INFO) << "kIsObscured:" << node.HasFlag(FLAGS_::kIsObscured); + FML_DLOG(INFO) << "kScopesRoute:" << node.HasFlag(FLAGS_::kScopesRoute); + FML_DLOG(INFO) << "kNamesRoute:" << node.HasFlag(FLAGS_::kNamesRoute); + FML_DLOG(INFO) << "kIsHidden:" << node.HasFlag(FLAGS_::kIsHidden); + FML_DLOG(INFO) << "kIsImage:" << node.HasFlag(FLAGS_::kIsImage); + FML_DLOG(INFO) << "kIsLiveRegion:" << node.HasFlag(FLAGS_::kIsLiveRegion); + FML_DLOG(INFO) << "kHasToggledState:" + << node.HasFlag(FLAGS_::kHasToggledState); + FML_DLOG(INFO) << "kIsToggled:" << node.HasFlag(FLAGS_::kIsToggled); + FML_DLOG(INFO) << "kHasImplicitScrolling:" + << node.HasFlag(FLAGS_::kHasImplicitScrolling); + FML_DLOG(INFO) << "kIsMultiline:" << node.HasFlag(FLAGS_::kIsMultiline); + FML_DLOG(INFO) << "kIsReadOnly:" << node.HasFlag(FLAGS_::kIsReadOnly); + FML_DLOG(INFO) << "kIsFocusable:" << node.HasFlag(FLAGS_::kIsFocusable); + FML_DLOG(INFO) << "kIsLink:" << node.HasFlag(FLAGS_::kIsLink); + FML_DLOG(INFO) << "kIsSlider:" << node.HasFlag(FLAGS_::kIsSlider); + FML_DLOG(INFO) << "kIsKeyboardKey:" << node.HasFlag(FLAGS_::kIsKeyboardKey); + FML_DLOG(INFO) << "kIsCheckStateMixed:" + << node.HasFlag(FLAGS_::kIsCheckStateMixed); + FML_DLOG(INFO) << "----------------SemanticsFlags--------------------"; +} + +void OhosAccessibilityBridge::GetCustomActionDebugInfo( + flutter::CustomAccessibilityAction customAccessibilityAction) +{ + FML_DLOG(INFO) << "--------------CustomAccessibilityAction------------"; + FML_DLOG(INFO) << "customAccessibilityAction.id=" + << customAccessibilityAction.id; + FML_DLOG(INFO) << "customAccessibilityAction.overrideId=" + << customAccessibilityAction.overrideId; + FML_DLOG(INFO) << "customAccessibilityAction.label=" + << customAccessibilityAction.label; + FML_DLOG(INFO) << "customAccessibilityAction.hint=" + << customAccessibilityAction.hint; + FML_DLOG(INFO) << "------------CustomAccessibilityAction--------------"; +} +} // namespace flutter diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h new file mode 100644 index 0000000000000000000000000000000000000000..602df9370bde6e53633d8b49868305b0aff5ec4a --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. + */ + +#ifndef OHOS_ACCESSIBILITY_BRIDGE_H +#define OHOS_ACCESSIBILITY_BRIDGE_H +#include +#include +#include +#include +#include +#include +#include "flutter/fml/mapping.h" +#include "flutter/lib/ui/semantics/custom_accessibility_action.h" +#include "flutter/lib/ui/semantics/semantics_node.h" +#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_manager.h" +#include "flutter/shell/platform/ohos/napi/platform_view_ohos_napi.h" + +namespace flutter { + +typedef flutter::SemanticsFlags FLAGS_; +typedef flutter::SemanticsAction ACTIONS_; +typedef flutter::SemanticsNode FLUTTER_NODE_; + +/** + * flutter和ohos的无障碍服务桥接 + */ +struct AbsoluteRect { + float left; + float top; + float right; + float bottom; + + static constexpr AbsoluteRect MakeEmpty() { + return AbsoluteRect{0.0, 0.0, 0.0, 0.0}; + } +}; + +struct SemanticsNodeExtent : flutter::SemanticsNode { + int32_t parentId = -1; + AbsoluteRect abRect = AbsoluteRect::MakeEmpty(); + + int32_t previousFlags; + int32_t previousActions; + int32_t previousTextSelectionBase; + int32_t previousTextSelectionExtent; + float previousScrollPosition; + float previousScrollExtentMax; + float previousScrollExtentMin; + std::string previousValue; + std::string previousLabel; +}; + +class OhosAccessibilityBridge { + public: + OhosAccessibilityBridge(); + ~OhosAccessibilityBridge(); + + static OhosAccessibilityBridge* GetInstance(); + + bool isOhosAccessibilityEnabled_; + bool IS_FLUTTER_NAVIGATE = false; + int64_t nativeShellHolder_; + ArkUI_AccessibilityProvider* provider_; + + void updateSemantics(flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions); + + void DispatchSemanticsAction(int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args); + + void announce(std::unique_ptr& message); + + // obtain the flutter semnatics node + flutter::SemanticsNode getOrCreateFlutterSemanticsNode(int32_t id); + + int32_t FindAccessibilityNodeInfosById( + int64_t elementId, + ArkUI_AccessibilitySearchMode mode, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList); + int32_t FindAccessibilityNodeInfosByText( + int64_t elementId, + const char* text, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList); + int32_t FindFocusedAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusType focusType, + int32_t requestId, + ArkUI_AccessibilityElementInfo* elementinfo); + int32_t FindNextFocusAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusMoveDirection direction, + int32_t requestId, + ArkUI_AccessibilityElementInfo* elementList); + int32_t ExecuteAccessibilityAction( + int64_t elementId, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments, + int32_t requestId); + int32_t ClearFocusedFocusAccessibilityNode(); + int32_t GetAccessibilityNodeCursorPosition(int64_t elementId, + int32_t requestId, + int32_t* index); + + void Flutter_SendAccessibilityAsyncEvent( + int64_t elementId, + ArkUI_AccessibilityEventType eventType); + void FlutterNodeToElementInfoById( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + int64_t elementId); + int32_t GetParentId(int64_t elementId); + + void ConvertChildRelativeRectToScreenRect(flutter::SemanticsNode node); + std::pair, std::pair> + GetAbsoluteScreenRect(int32_t flutterNodeId); + void SetAbsoluteScreenRect(int32_t flutterNodeId, + float left, + float top, + float right, + float bottom); + + SemanticsNodeExtent SetAndGetSemanticsNodeExtent(flutter::SemanticsNode node); + + void FlutterScrollExecution( + flutter::SemanticsNode node, + ArkUI_AccessibilityElementInfo* elementInfoFromList); + + void ClearFlutterSemanticsCaches(); + + private: + static OhosAccessibilityBridge bridgeInstance; + static const int32_t ROOT_NODE_ID = 0; + constexpr static const double SCROLL_EXTENT_FOR_INFINITY = 100000.0; + constexpr static const double SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0; + + flutter::SemanticsNode inputFocusedNode; + flutter::SemanticsNode lastInputFocusedNode; + flutter::SemanticsNode accessibilityFocusedNode; + + std::shared_ptr ax_manager_; + std::vector> parentChildIdVec; + std::unordered_map flutterSemanticsTree_; + std::unordered_map< + int32_t, + std::pair, std::pair>> + screenRectMap_; + std::unordered_map actions_mp_; + std::vector flutterNavigationVec_; + + const std::map + ArkUI_ACTION_TYPE_MAP_ = { + {"invalid", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_INVALID}, + {"click", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK}, + {"long press", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_LONG_CLICK}, + {"focus acquisition", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_GAIN_ACCESSIBILITY_FOCUS}, + {"focus clearance", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLEAR_ACCESSIBILITY_FOCUS}, + {"forward scroll", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD}, + {"backward scroll", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD}, + {"copy text", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY}, + {"paste text", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE}, + {"cut text", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT}, + {"text selection", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SELECT_TEXT}, + {"set text", ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT}, + {"text cursor position setting", + ArkUI_Accessibility_ActionType:: + ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION}, + }; + + static const int32_t FOCUSABLE_FLAGS = + static_cast(FLAGS_::kHasCheckedState) | + static_cast(FLAGS_::kIsChecked) | + static_cast(FLAGS_::kIsSelected) | + static_cast(FLAGS_::kIsTextField) | + static_cast(FLAGS_::kIsFocused) | + static_cast(FLAGS_::kHasEnabledState) | + static_cast(FLAGS_::kIsEnabled) | + static_cast(FLAGS_::kIsInMutuallyExclusiveGroup) | + static_cast(FLAGS_::kHasToggledState) | + static_cast(FLAGS_::kIsToggled) | + static_cast(FLAGS_::kHasToggledState) | + static_cast(FLAGS_::kIsFocusable) | + static_cast(FLAGS_::kIsSlider); + + static const int32_t SCROLLABLE_ACTIONS = + static_cast(ACTIONS_::kScrollUp) | + static_cast(ACTIONS_::kScrollDown) | + static_cast(ACTIONS_::kScrollLeft) | + static_cast(ACTIONS_::kScrollRight); + + void FlutterSetElementInfoProperties( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + int64_t elementId); + void FlutterSetElementInfoOperationActions( + ArkUI_AccessibilityElementInfo* elementInfoFromList, + std::string widget_type); + void FlutterTreeToArkuiTree( + ArkUI_AccessibilityElementInfoList* elementInfoList); + + flutter::SemanticsNode getFlutterRootSemanticsNode(); + std::string GetNodeComponentType(const flutter::SemanticsNode& node); + flutter::SemanticsAction ArkuiActionsToFlutterActions( + ArkUI_Accessibility_ActionType arkui_action); + + bool HasScrolled(const flutter::SemanticsNode& flutterNode); + + bool IsNodeFocusable(const flutter::SemanticsNode& flutterNode); + bool IsNodeCheckable(flutter::SemanticsNode flutterNode); + bool IsNodeChecked(flutter::SemanticsNode flutterNode); + bool IsNodeSelected(flutter::SemanticsNode flutterNode); + bool IsNodeClickable(flutter::SemanticsNode flutterNode); + bool IsNodeScrollable(flutter::SemanticsNode flutterNode); + bool IsNodePassword(flutter::SemanticsNode flutterNode); + bool IsNodeVisible(flutter::SemanticsNode flutterNode); + bool IsNodeEnabled(flutter::SemanticsNode flutterNode); + bool IsNodeHasLongPress(flutter::SemanticsNode flutterNode); + + bool IsTextField(flutter::SemanticsNode flutterNode); + bool IsSlider(flutter::SemanticsNode flutterNode); + bool IsScrollableWidget(flutter::SemanticsNode flutterNode); + void PerformSetText(flutter::SemanticsNode flutterNode, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments); + void PerformSelectText(flutter::SemanticsNode flutterNode, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments); + + void AddRouteNodes(std::vector edges, + flutter::SemanticsNode node); + std::string GetRouteName(flutter::SemanticsNode node); + void onWindowNameChange(flutter::SemanticsNode route); + void removeSemanticsNode(flutter::SemanticsNode nodeToBeRemoved); + + void GetSemanticsNodeDebugInfo(flutter::SemanticsNode node); + void GetSemanticsFlagsDebugInfo(flutter::SemanticsNode node); + void GetCustomActionDebugInfo( + flutter::CustomAccessibilityAction customAccessibilityAction); + + void PageStateUpdate(int64_t elementId); + void RequestFocusWhenPageUpdate(); + + bool Contains(const std::string source, const std::string target); +}; + +} // namespace flutter +#endif // OHOS_ACCESSIBILITY_BRIDGE_H diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_features.cpp b/shell/platform/ohos/accessibility/ohos_accessibility_features.cpp new file mode 100644 index 0000000000000000000000000000000000000000..caea517fee6a71dc676e50924a31f6757a2805a1 --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_features.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 "flutter/shell/platform/ohos/accessibility/ohos_accessibility_features.h" +#include "flutter/shell/platform/ohos/ohos_shell_holder.h" +#include "flutter/fml/logging.h" + +namespace flutter { + + OhosAccessibilityFeatures OhosAccessibilityFeatures::instance; + + OhosAccessibilityFeatures::OhosAccessibilityFeatures() {}; + OhosAccessibilityFeatures::~OhosAccessibilityFeatures() {}; + + OhosAccessibilityFeatures* OhosAccessibilityFeatures::GetInstance() { + return &OhosAccessibilityFeatures::instance; + } + + /** + * bold text for AccessibilityFeature + */ + void OhosAccessibilityFeatures::SetBoldText(double fontWeightScale, int64_t shell_holder_id) { + bool shouldBold = fontWeightScale > 1.0; + + if (shouldBold) { + accessibilityFeatureFlags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); + FML_DLOG(INFO) << "SetBoldText -> accessibilityFeatureFlags: "<(flutter::AccessibilityFeatureFlag::kBoldText); + } + + SendAccessibilityFlags(shell_holder_id); + } + + /** + * send the accessibility flags to flutter dart sdk + */ + void OhosAccessibilityFeatures::SendAccessibilityFlags(int64_t shell_holder_id) { + auto ohos_shell_holder = reinterpret_cast(shell_holder_id); + ohos_shell_holder->GetPlatformView()->PlatformView::SetAccessibilityFeatures(accessibilityFeatureFlags); + FML_DLOG(INFO) << "SendAccessibilityFlags -> accessibilityFeatureFlags = " + << accessibilityFeatureFlags; + accessibilityFeatureFlags = 0; + } + +} \ No newline at end of file diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_features.h b/shell/platform/ohos/accessibility/ohos_accessibility_features.h new file mode 100644 index 0000000000000000000000000000000000000000..093e89ce11d37c02f40d89151372a3433229f9ff --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_features.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. + */ +#ifndef OHOS_ACCESSIBILITY_FEATURES_H +#define OHOS_ACCESSIBILITY_FEATURES_H +#include +#include "flutter/lib/ui/window/platform_configuration.h" +#include "flutter/shell/platform/ohos/napi/platform_view_ohos_napi.h" + +namespace flutter { + +class OhosAccessibilityFeatures { + public: + OhosAccessibilityFeatures(); + ~OhosAccessibilityFeatures(); + + static OhosAccessibilityFeatures* GetInstance(); + + void SetBoldText(double fontWeightScale, int64_t shell_holder_id); + void SendAccessibilityFlags(int64_t shell_holder_id); + + private: + static OhosAccessibilityFeatures instance; + + // Font weight adjustment (FontWeight.Bold - FontWeight.Normal = w700 - w400 = 300) + static const int32_t BOLD_TEXT_WEIGHT_ADJUSTMENT = 300; + int32_t accessibilityFeatureFlags = 0; +}; + +} + +#endif \ No newline at end of file diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_manager.cpp b/shell/platform/ohos/accessibility/ohos_accessibility_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e636097658b3fe54ef80062b79e8fd5649ce1f8 --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_manager.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 "flutter/shell/platform/ohos/accessibility/ohos_accessibility_manager.h" +#include "flutter/fml/logging.h" + +namespace flutter { +OhosAccessibilityManager::OhosAccessibilityManager() {}; +OhosAccessibilityManager::~OhosAccessibilityManager() {}; + +void OhosAccessibilityManager::onAccessibilityStateChanged( + bool isOhosAccessibilityEnabled) {}; + +void OhosAccessibilityManager::setOhosAccessibilityEnabled(bool isEnabled) { + FML_DLOG(INFO) << "OhosAccessibilityManager::setOhosAccessibilityEnabled = " + << isEnabled; + this->isOhosAccessibilityEnabled_ = isEnabled; +} + +bool OhosAccessibilityManager::getOhosAccessibilityEnabled() { + return this->isOhosAccessibilityEnabled_; +} + +} // namespace flutter diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_manager.h b/shell/platform/ohos/accessibility/ohos_accessibility_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..d32e76173e6a3fa363437c534e0467b01e5ce31f --- /dev/null +++ b/shell/platform/ohos/accessibility/ohos_accessibility_manager.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. + */ +#ifndef OHOS_ACCESSIBILITY_MANAGER_H +#define OHOS_ACCESSIBILITY_MANAGER_H + + +namespace flutter { +/** + * 无障碍辅助管理类 + */ +class OhosAccessibilityManager { + public: + OhosAccessibilityManager(); + ~OhosAccessibilityManager(); + + void onAccessibilityStateChanged(bool isOhosAccessibilityEnabled); + bool getOhosAccessibilityEnabled(); + void setOhosAccessibilityEnabled(bool isEnabled); + + private: + bool isOhosAccessibilityEnabled_ = false; +}; + +} // namespace flutter + +#endif \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index.d.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index.d.ets index 53a5eabeca7ef8c0b205a413100d8693733f1c4a..6786bedf54807514a6452c523f711ce2f2809a6c 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index.d.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index.d.ets @@ -17,6 +17,7 @@ import common from '@ohos.app.ability.common'; import resourceManager from '@ohos.resourceManager'; import image from '@ohos.multimedia.image'; import FlutterNapi from '../../../ets/embedding/engine/FlutterNapi'; +import { ByteBuffer } from '../../../ets/util/ByteBuffer'; import { FlutterCallbackInformation } from '../../../ets/view/FlutterCallbackInformation'; export const getContext: (a: number) => napiContext; @@ -110,6 +111,12 @@ export const nativeXComponentDispatchMouseWheel: (nativeShellHolderId: number, timestamp: number ) => void; + +// send updateSemantics and updateCustomAccessibilityActions from ets to c++ +export const nativeUpdateSemantics: (buffer: ByteBuffer, strings: string[], stringAttributeArgs: ByteBuffer[]) => void; +export const nativeUpdateCustomAccessibilityActions: (buffer: ByteBuffer, strings: string[]) => void; + + /** * Detaches flutterNapi和engine之间的关联 * 这个方法执行前提是flutterNapi已经和engine关联 @@ -134,4 +141,21 @@ export const nativeDecodeUtf8: (array: Uint8Array) => string; export const nativeSetTextureBufferSize: (nativeShellHolderId: number, textureId: number, width: number, height: number) => void; -export const nativeLookupCallbackInformation: (callback: FlutterCallbackInformation, handler: number) => number; \ No newline at end of file +/** + * accessibiltyChannel中的 + */ +export const nativeSetAccessibilityFeatures: (accessibilityFeatureFlags: number, responseId: number) => void; + +export const nativeAccessibilityStateChange: (state: Boolean) => void; + +export const nativeAnnounce: (message: string) => void; + +export const nativeSetSemanticsEnabled: (nativeShellHolderId: number, enabled: boolean) => void; + +export const nativeSetFontWeightScale: (nativeShellHolderId: number, fontWeightScale: number) => void; + +export const nativeGetShellHolderId: (nativeShellHolderId: number) => void; + +export const nativeLookupCallbackInformation: (callback: FlutterCallbackInformation, handler: number) => number; + +export const nativeGetFlutterNavigationAction: (isNavigate: boolean) => void; diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ets index 78f1a520e7ec6efbc792f1164c8933dafc439d6d..e95bdfbf4b6748a951af0a2d0ef36198b626a2ea 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ets @@ -22,7 +22,7 @@ import { FlutterCallbackInformation } from '../../view/FlutterCallbackInformatio import image from '@ohos.multimedia.image'; import { EngineLifecycleListener } from './FlutterEngine'; import { ByteBuffer } from '../../util/ByteBuffer'; -import { Action } from '../../view/AccessibilityBridge' +import { AccessibilityManager, Action } from '../../view/AccessibilityBridge' import LocalizationPlugin from '../../plugin/localization/LocalizationPlugin'; import i18n from '@ohos.i18n'; import Any from '../../plugin/common/Any'; @@ -52,8 +52,10 @@ export default class FlutterNapi { private engineLifecycleListeners = new Set(); accessibilityDelegate: AccessibilityDelegate | null = null; localizationPlugin: LocalizationPlugin | null = null; - isDisplayingFlutterUi: boolean = false; + accessibilityManager: AccessibilityManager | null = null; + + /** * 更新刷新率 * @param rate @@ -320,9 +322,10 @@ export default class FlutterNapi { } } - setSemanticsEnabled(enabled: boolean, responseId: number): void { + setSemanticsEnabledWithRespId(enabled: boolean, responseId: number): void { + this.ensureRunningOnMainThread(); if (this.isAttached()) { - this.nativeSetSemanticsEnabled(enabled); + flutter.nativeSetSemanticsEnabled(this.nativeShellHolderId!, enabled); } else { Log.w( TAG, @@ -331,12 +334,21 @@ export default class FlutterNapi { } } - // Send an empty response to a platform message received from Dart. - nativeSetSemanticsEnabled(enabled: boolean):void {} + setSemanticsEnabled(enabled: boolean): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + flutter.nativeSetSemanticsEnabled(this.nativeShellHolderId!, enabled); + } else { + Log.e( + TAG, + "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send."); + } + } setAccessibilityFeatures(accessibilityFeatureFlags: number, responseId: number): void { if (this.isAttached()) { - this.nativeSetAccessibilityFeatures(accessibilityFeatureFlags, responseId); + // this.nativeSetAccessibilityFeatures(accessibilityFeatureFlags, responseId); + flutter.nativeSetAccessibilityFeatures(accessibilityFeatureFlags, responseId); } else { Log.w( TAG, @@ -371,6 +383,15 @@ export default class FlutterNapi { } } + accessibilityStateChange(state: Boolean): void { + this.ensureRunningOnMainThread(); + if(this.accessibilityDelegate != null) { + this.accessibilityDelegate.accessibilityStateChange(state); + } + Log.d(TAG, "accessibilityStateChange is called"); + flutter.nativeAccessibilityStateChange(state); + } + setLocalizationPlugin(localizationPlugin: LocalizationPlugin | null): void { this.localizationPlugin = localizationPlugin; } @@ -502,10 +523,39 @@ export default class FlutterNapi { } } + getShellHolderId(): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + flutter.nativeGetShellHolderId(this.nativeShellHolderId!); + } + } + + setFontWeightScale(fontWeightScale: number): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + Log.i(TAG, "setFontWeightScale: " + fontWeightScale); + flutter.nativeSetFontWeightScale(this.nativeShellHolderId!, fontWeightScale); + } else { + Log.w(TAG, "setFontWeightScale is detached !"); + } + } + + setFlutterNavigationAction(isNavigate: boolean): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + Log.i(TAG, "setFlutterNavigationAction: " + isNavigate); + flutter.nativeGetFlutterNavigationAction(isNavigate); + } else { + Log.w(TAG, "setFlutterNavigationAction is detached !"); + } + } + } export interface AccessibilityDelegate { updateCustomAccessibilityActions(buffer: ByteBuffer, strings: string[]): void; updateSemantics(buffer: ByteBuffer, strings: string[], stringAttributeArgs: ByteBuffer[]): void; + + accessibilityStateChange(state: Boolean): void; } diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ets index d2894ebcca73e04dc907e9034a3f251a45d0c825..a1773db5c745c9111fcc3fda82ccde07e0d50bd2 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ets @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ErrorEvent, Queue, taskpool, worker, MessageEvents } from '@kit.ArkTS'; +import { ErrorEvent, Queue, taskpool, worker, MessageEvents, JSON } from '@kit.ArkTS'; import Log from '../../../util/Log'; import { BinaryMessageHandler, BinaryMessenger, BinaryReply, TaskPriority, TaskQueue, TaskQueueOptions } from '../../../plugin/common/BinaryMessenger'; @@ -97,6 +97,7 @@ export class DartMessenger implements BinaryMessenger, PlatformMessageHandler { } finally { TraceSection.endWithId("DartMessenger#send on " + channel, traceId); } + this.IsFlutterNavigationExecuted(channel); } dispatchMessageToQueue(handlerInfo: HandlerInfo, message: ArrayBuffer, replyId: number): void { @@ -127,6 +128,7 @@ export class DartMessenger implements BinaryMessenger, PlatformMessageHandler { } else { this.invokeHandler(handlerInfo?.handler as BinaryMessageHandler, message, replyId); } + this.IsFlutterNavigationExecuted(channel); } handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void { @@ -157,6 +159,14 @@ export class DartMessenger implements BinaryMessenger, PlatformMessageHandler { getPendingChannelResponseCount(): number { return this.pendingReplies.size; } + + //获取当前flutter页面是否路由跳转,并传递到native侧 + IsFlutterNavigationExecuted(channel: String): void { + if(channel == "flutter/navigation") { + this.flutterNapi.setFlutterNavigationAction(true); + Log.d(TAG, "setFlutterNavigationAction -> '" + channel + "'"); + } + } } /** diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets index e64bb55975f5f592e96e55ee8e53fbbbb023137d..9d00c43494d9784aadd88ee16b3f41ae561054f8 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets @@ -105,6 +105,7 @@ export class FlutterRenderer implements TextureRegistry { }) }) } + } export class SurfaceTextureRegistryEntry implements SurfaceTextureEntry { diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets index 9b2f9ee6cfdcfa5bbee616474617b936344c7e42..bbc5a658ea5c9af1e99f69a45e737b861fabcfd8 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets @@ -22,6 +22,8 @@ import { Action } from '../../../view/AccessibilityBridge' import StandardMessageCodec from '../../../plugin/common/StandardMessageCodec'; import StringUtils from '../../../util/StringUtils'; import Any from '../../../plugin/common/Any'; +import flutter from 'libflutter.so'; +import { ByteBuffer } from '../../../util/ByteBuffer'; /** * 辅助功能channel @@ -31,12 +33,12 @@ export default class AccessibilityChannel implements MessageHandler{ private static CHANNEL_NAME = "flutter/accessibility"; private channel: BasicMessageChannel; private flutterNapi: FlutterNapi; - private handler: AccessibilityMessageHandler | null = null; + private handler: AccessibilityMessageHandler = new DefaultHandler(); private nextReplyId: number = 1; onMessage(message: object, reply: Reply): void { if (this.handler == null) { - Log.i(AccessibilityChannel.TAG, "NULL"); + Log.i(AccessibilityChannel.TAG, "handler = NULL"); reply.reply(StringUtils.stringToArrayBuffer("")); return; } @@ -50,6 +52,7 @@ export default class AccessibilityChannel implements MessageHandler{ Log.i(AccessibilityChannel.TAG, "Announce"); let announceMessage: string = data.get("message"); if (announceMessage != null) { + Log.i(AccessibilityChannel.TAG, "message is " + announceMessage); this.handler.announce(announceMessage); } break; @@ -91,17 +94,20 @@ export default class AccessibilityChannel implements MessageHandler{ onOhosAccessibilityEnabled(): void { let replyId: number = this.nextReplyId++; - this.flutterNapi.setSemanticsEnabled(true, replyId); + this.flutterNapi.setSemanticsEnabledWithRespId(true, replyId); + Log.i(AccessibilityChannel.TAG, "onOhosAccessibilityEnabled = true"); } onOhosAccessibilityFeatures(accessibilityFeatureFlags: number): void { let replyId: number = this.nextReplyId++; this.flutterNapi.setAccessibilityFeatures(accessibilityFeatureFlags, replyId); + Log.i(AccessibilityChannel.TAG, "onOhosAccessibilityFeatures"); } dispatchSemanticsAction(virtualViewId: number, action: Action): void { let replyId: number = this.nextReplyId++; this.flutterNapi.dispatchSemanticsAction(virtualViewId, action, replyId); + Log.i(AccessibilityChannel.TAG, "dispatchSemanticsAction"); } setAccessibilityMessageHandler(handler: AccessibilityMessageHandler): void { @@ -112,9 +118,35 @@ export default class AccessibilityChannel implements MessageHandler{ } -interface AccessibilityMessageHandler extends AccessibilityDelegate { +export interface AccessibilityMessageHandler extends AccessibilityDelegate { announce(message: string): void; onTap(nodeId: number): void; onLongPress(nodeId: number): void; onTooltip(nodeId: string): void; +} + +export class DefaultHandler implements AccessibilityMessageHandler { + private static TAG = "AccessibilityMessageHandler"; + announce(message: string): void { + Log.i(DefaultHandler.TAG, "handler announce."); + flutter.nativeAnnounce(message); + } + onTap(nodeId: number): void { + Log.i(DefaultHandler.TAG, "handler onTap."); + } + onLongPress(nodeId: number): void { + Log.i(DefaultHandler.TAG, "handler onLongPress."); + } + onTooltip(nodeId: string): void { + Log.i(DefaultHandler.TAG, "handler onTooltip."); + } + updateSemantics(buffer: ByteBuffer, strings: string[], stringAttributeArgs: ByteBuffer[]): void { + Log.i(DefaultHandler.TAG, "handler updateSemantics"); + } + updateCustomAccessibilityActions(buffer: ByteBuffer, strings: string[]): void { + Log.i(DefaultHandler.TAG, "handler updateCustomAccessibilityActions"); + } + accessibilityStateChange(state: Boolean): void{ + Log.i(DefaultHandler.TAG, "handler accessibilityStateChange"); + } } \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ets index 8ba4463178e5737bd4e4e4d4f27aa6457c67ef83..8a336a529738df5ad8a30877b87cab08762b6058 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ets @@ -36,6 +36,8 @@ import appRecovery from '@ohos.app.ability.appRecovery'; import FlutterManager from './FlutterManager'; import { FlutterView } from '../../view/FlutterView'; import ApplicationInfoLoader from '../engine/loader/ApplicationInfoLoader'; +import { accessibility } from '@kit.AccessibilityKit'; +import { AccessibilityManager } from '../../view/AccessibilityBridge'; const TAG = "FlutterAbility"; const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS'; @@ -51,6 +53,7 @@ export class FlutterAbility extends UIAbility implements Host { private flutterView: FlutterView | null = null; private mainWindow?: window.Window | null; private errorManagerId:number = 0; + private accessibilityManager?: AccessibilityManager | null; getFlutterView(): FlutterView | null { return this.flutterView; @@ -70,6 +73,7 @@ export class FlutterAbility extends UIAbility implements Host { async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { // 冷启动通过上下文环境获取到系统当前文字大小,并进行键值存储 AppStorage.setOrCreate('fontSizeScale', this.context.config.fontSizeScale); + Log.i(TAG, "this.context.config.fontSizeScale = " + this.context.config.fontSizeScale); Log.i(TAG, "bundleCodeDir=" + this.context.bundleCodeDir); FlutterManager.getInstance().pushUIAbility(this) @@ -108,6 +112,15 @@ export class FlutterAbility extends UIAbility implements Host { if (flutterApplicationInfo.isDebugMode) { this.delegate?.initWindow(); } + + //冷启动对os是否开启无障碍服务进行查询 + let accessibilityState: boolean = accessibility.isOpenAccessibilitySync(); + if(accessibilityState) { + this.delegate?.getFlutterNapi()?.setSemanticsEnabled(accessibilityState); + } + Log.i(TAG, `accessibility isOpen state -> ${JSON.stringify(accessibilityState)}`); + + this.accessibilityManager = new AccessibilityManager(); } onDestroy() { @@ -377,6 +390,10 @@ export class FlutterAbility extends UIAbility implements Host { .setTextScaleFactor(config.fontSizeScale == undefined? 1.0 : config.fontSizeScale) .send(); //热启动生命周期内,实时监听系统设置环境改变并实时发送相应信息 + //实时获取系统字体加粗系数 + this.delegate?.getFlutterNapi()?.setFontWeightScale(config.fontWeightScale == undefined? 0 : config.fontWeightScale); + Log.i(TAG, 'fontWeightScale: ' + JSON.stringify(config.fontWeightScale)); + if (config.language != '') { this.getFlutterEngine()?.getLocalizationPlugin()?.sendLocaleToFlutter(); } diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/AccessibilityEventsDelegate.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/AccessibilityEventsDelegate.ets index ea9450d4aa0f140dceeb4a7925f46383a65d0ea3..aae15ba935e29f71d8f734b5505b8236b49af2d7 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/AccessibilityEventsDelegate.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/AccessibilityEventsDelegate.ets @@ -32,7 +32,7 @@ export class AccessibilityEventsDelegate { return true; } - setAccessibilityBridge (accessibilityBridge: AccessibilityBridge): void { + setAccessibilityBridge (accessibilityBridge: AccessibilityBridge | null): void { this.accessibilityBridge = accessibilityBridge; } } \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/PlatformViewsController.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/PlatformViewsController.ets index 8afcd8704308748e02eb41e0d5f2dea566814d63..79c10a4bddfec1b295bfbee0020b08db54e62e12 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/PlatformViewsController.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/platform/PlatformViewsController.ets @@ -115,11 +115,11 @@ export default class PlatformViewsController implements PlatformViewsAccessibili } attachAccessibilityBridge(accessibilityBridge: AccessibilityBridge): void { - throw new Error('Method not implemented.'); + this.accessibilityEventsDelegate.setAccessibilityBridge(accessibilityBridge); } detachAccessibilityBridge(): void { - throw new Error('Method not implemented.'); + this.accessibilityEventsDelegate.setAccessibilityBridge(null); } createForPlatformViewLayer(request: PlatformViewCreationRequest): void { diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ets index 0e08f41fe9171658365b4e2c806b075807ddc423..c7122f48701bd5fa6365bcb72e97a38b460ef132 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ets @@ -12,11 +12,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import AccessibilityChannel, {AccessibilityMessageHandler} from '../embedding/engine/systemchannels/AccessibilityChannel'; +import { ByteBuffer } from '../util/ByteBuffer'; +import Log from '../util/Log'; + +const TAG = "AccessibilityBridge"; + +export default class AccessibilityBridge implements AccessibilityMessageHandler { + + private accessibilityChannel: AccessibilityChannel | null = null; -export default class AccessibilityBridge { constructor(){ } + + announce(message: string): void { + throw new Error('Method not implemented.'); + // android -> rootAccessibilityView.announceForAccessibility(message); + } + + onTap(nodeId: number): void { + throw new Error('Method not implemented.'); + // android -> sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_CLICKED); + } + + onLongPress(nodeId: number): void { + throw new Error('Method not implemented.'); + // android -> sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + } + + onTooltip(nodeId: string): void { + throw new Error('Method not implemented.'); + } + + updateSemantics(buffer: ByteBuffer, strings: string[], stringAttributeArgs: ByteBuffer[]): void { + Log.d(TAG, "AccessibilityBridge.ets updateSemantics is called"); + } + + updateCustomAccessibilityActions(buffer: ByteBuffer, strings: string[]): void { + Log.d(TAG, "AccessibilityBridge.ets updateCustomAccessibilityActions is called"); + + } + + accessibilityStateChange(state: Boolean): void{ + Log.d(TAG, "AccessibilityBridge.ets accessibilityStateChange is called"); + } + +} + +export class AccessibilityManager { + private fontWeightScale: number | null = null; + + setFontWeightScale(fontWeightScale: number): void { + this.fontWeightScale = fontWeightScale; + Log.i(TAG, 'setFontWeightScale: ' + JSON.stringify(this.fontWeightScale)); + } + + getFontWeightScale(): number { + Log.i(TAG, 'getFontWeightScale: ' + JSON.stringify(this.fontWeightScale)); + return this.fontWeightScale!; + } } export enum Action { diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterView.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterView.ets index 0f76a36f534ba5aec81a9550c560ea66e46c42f1..6f682d78c22d4bda55141a0e7bb389c2de172444 100644 --- a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterView.ets +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterView.ets @@ -25,6 +25,7 @@ import ArrayList from '@ohos.util.ArrayList'; import { EmbeddingNodeController } from '../embedding/ohos/EmbeddingNodeController'; import PlatformView, { Params } from '../plugin/platform/PlatformView'; import { JSON } from '@kit.ArkTS'; +import { accessibility } from '@kit.AccessibilityKit'; const TAG = "FlutterViewTag"; @@ -149,6 +150,12 @@ export class FlutterView { this.mainWindow?.on('windowSizeChange', this.windowSizeChangeCallback); this.mainWindow?.on('avoidAreaChange', this.avoidAreaChangeCallback); this.mainWindow?.on('windowStatusChange', this.windowStatusChangeCallback); + + //监听系统无障碍服务状态改变 + accessibility.on('accessibilityStateChange', (data: boolean) => { + Log.i(TAG, `subscribe accessibility state change, result: ${JSON.stringify(data)}`); + this.flutterEngine?.getFlutterNapi()?.setSemanticsEnabled(data); + }); this.systemAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); this.navigationAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); this.gestureAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM_GESTURE); @@ -242,6 +249,9 @@ export class FlutterView { this.mainWindow?.off('windowSizeChange', this.windowSizeChangeCallback); this.mainWindow?.off('avoidAreaChange', this.avoidAreaChangeCallback); this.mainWindow?.off('windowStatusChange', this.windowStatusChangeCallback); + accessibility.off('accessibilityStateChange', (data: boolean) => { + Log.i(TAG, `unsubscribe accessibility state change, result: ${JSON.stringify(data)}`); + }); } catch (e) { Log.e(TAG, "mainWindow off error: " + JSON.stringify(e)); } diff --git a/shell/platform/ohos/library_loader.cpp b/shell/platform/ohos/library_loader.cpp index 473feb562ef2e514295f71ca29a328f405596f71..3502372782fc6af77e10721905e1c888e293e7c1 100644 --- a/shell/platform/ohos/library_loader.cpp +++ b/shell/platform/ohos/library_loader.cpp @@ -17,8 +17,8 @@ #include "flutter/shell/platform/ohos/ohos_main.h" #include "napi/native_api.h" #include "napi_common.h" -#include "ohos_xcomponent_adapter.h" #include "ohos_logging.h" +#include "ohos_xcomponent_adapter.h" // namespace flutter { @@ -132,15 +132,41 @@ static napi_value Init(napi_env env, napi_value exports) { DECLARE_NAPI_FUNCTION( "nativeSetTextureBackGroundPixelMap", flutter::PlatformViewOHOSNapi::nativeSetTextureBackGroundPixelMap), + DECLARE_NAPI_FUNCTION("nativeEncodeUtf8", + flutter::PlatformViewOHOSNapi::nativeEncodeUtf8), + DECLARE_NAPI_FUNCTION("nativeDecodeUtf8", + flutter::PlatformViewOHOSNapi::nativeDecodeUtf8), + DECLARE_NAPI_FUNCTION( + "nativeUpdateSemantics", + flutter::PlatformViewOHOSNapi::nativeUpdateSemantics), + DECLARE_NAPI_FUNCTION( + "nativeUpdateCustomAccessibilityActions", + flutter::PlatformViewOHOSNapi::nativeUpdateCustomAccessibilityActions), + DECLARE_NAPI_FUNCTION( + "nativeAccessibilityStateChange", + flutter::PlatformViewOHOSNapi::nativeAccessibilityStateChange), + DECLARE_NAPI_FUNCTION( + "nativeAnnounce", + flutter::PlatformViewOHOSNapi::nativeAnnounce), DECLARE_NAPI_FUNCTION( - "nativeEncodeUtf8", - flutter::PlatformViewOHOSNapi::nativeEncodeUtf8), + "nativeSetSemanticsEnabled", + flutter::PlatformViewOHOSNapi::nativeSetSemanticsEnabled), + DECLARE_NAPI_FUNCTION( + "nativeSetFontWeightScale", + flutter::PlatformViewOHOSNapi::nativeSetFontWeightScale), + DECLARE_NAPI_FUNCTION( + "nativeGetShellHolderId", + flutter::PlatformViewOHOSNapi::nativeGetShellHolderId), DECLARE_NAPI_FUNCTION( "nativeDecodeUtf8", flutter::PlatformViewOHOSNapi::nativeDecodeUtf8), - DECLARE_NAPI_FUNCTION( + DECLARE_NAPI_FUNCTION( "nativeLookupCallbackInformation", flutter::PlatformViewOHOSNapi::nativeLookupCallbackInformation), + DECLARE_NAPI_FUNCTION( + "nativeGetFlutterNavigationAction", + flutter::PlatformViewOHOSNapi::nativeGetFlutterNavigationAction), + }; FML_DLOG(INFO) << "Init NAPI size=" << sizeof(desc) / sizeof(desc[0]); diff --git a/shell/platform/ohos/napi/platform_view_ohos_napi.cpp b/shell/platform/ohos/napi/platform_view_ohos_napi.cpp index d46617ef3a738051ec6cfe7fff79c14bea7b4778..18977911e54dacf1c157dca144cb8116c4959249 100644 --- a/shell/platform/ohos/napi/platform_view_ohos_napi.cpp +++ b/shell/platform/ohos/napi/platform_view_ohos_napi.cpp @@ -25,14 +25,14 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/platform/ohos/napi_util.h" +#include "flutter/shell/platform/ohos/ohos_logging.h" #include "flutter/shell/platform/ohos/ohos_main.h" #include "flutter/shell/platform/ohos/ohos_shell_holder.h" +#include "flutter/shell/platform/ohos/ohos_xcomponent_adapter.h" #include "flutter/shell/platform/ohos/surface/ohos_native_window.h" #include "flutter/shell/platform/ohos/types.h" #include "flutter/lib/ui/plugins/callback_cache.h" #include "unicode/uchar.h" -#include "flutter/shell/platform/ohos/ohos_xcomponent_adapter.h" -#include "flutter/shell/platform/ohos/ohos_logging.h" #define OHOS_SHELL_HOLDER (reinterpret_cast(shell_holder)) namespace flutter { @@ -354,6 +354,7 @@ std::vector splitString(const std::string& input, char delimiter) { return result; } + flutter::locale PlatformViewOHOSNapi::resolveNativeLocale( std::vector supportedLocales) { if (supportedLocales.empty()) { @@ -416,9 +417,10 @@ void PlatformViewOHOSNapi::DecodeImage(int64_t imageGeneratorAddress, void* inputData, size_t dataSize) { FML_DLOG(INFO) << "start decodeImage"; - platform_task_runner_->PostTask(fml::MakeCopyable( - [imageGeneratorAddress_ = imageGeneratorAddress, - inputData_ = std::move(inputData), dataSize_ = dataSize, this]() mutable { + platform_task_runner_->PostTask( + fml::MakeCopyable([imageGeneratorAddress_ = imageGeneratorAddress, + inputData_ = std::move(inputData), + dataSize_ = dataSize, this]() mutable { napi_value callbackParam[2]; callbackParam[0] = @@ -436,7 +438,9 @@ void PlatformViewOHOSNapi::DecodeImage(int64_t imageGeneratorAddress, })); } -void PlatformViewOHOSNapi::FlutterViewOnTouchEvent(std::shared_ptr touchPacketString, int size) { +void PlatformViewOHOSNapi::FlutterViewOnTouchEvent( + std::shared_ptr touchPacketString, + int size) { if (touchPacketString == nullptr) { FML_LOG(ERROR) << "Input parameter error"; return; @@ -446,11 +450,13 @@ void PlatformViewOHOSNapi::FlutterViewOnTouchEvent(std::shared_ptr( OhosMain::Get().GetSettings(), napi_facade, platform_loop); if (shell_holder->IsValid()) { - int64_t shell_holder_value = - reinterpret_cast(shell_holder.get()); + int64_t shell_holder_value = reinterpret_cast(shell_holder.get()); FML_DLOG(INFO) << "PlatformViewOHOSNapi shell_holder:" << shell_holder_value; napi_value id; @@ -579,7 +584,6 @@ napi_value PlatformViewOHOSNapi::nativeUpdateOhosAssetManager( napi_callback_info info) { LOGD("PlatformViewOHOSNapi::nativeUpdateOhosAssetManager"); - // TODO: return nullptr; } @@ -590,14 +594,14 @@ napi_value PlatformViewOHOSNapi::nativeGetPixelMap(napi_env env, napi_callback_info info) { LOGD("PlatformViewOHOSNapi::nativeGetPixelMap"); - // TODO: return nullptr; } /** * 从当前的flutterNapi复制一个新的实例 */ -napi_value PlatformViewOHOSNapi::nativeSpawn(napi_env env, napi_callback_info info) { +napi_value PlatformViewOHOSNapi::nativeSpawn(napi_env env, + napi_callback_info info) { napi_status ret; size_t argc = 6; napi_value args[6] = {nullptr}; @@ -637,12 +641,14 @@ napi_value PlatformViewOHOSNapi::nativeSpawn(napi_env env, napi_callback_info in LOGD(" initialRoute: %{public}s", initial_route.c_str()); std::vector entrypoint_args; - if (fml::napi::SUCCESS != fml::napi::GetArrayString(env, args[4], entrypoint_args)) { + if (fml::napi::SUCCESS != + fml::napi::GetArrayString(env, args[4], entrypoint_args)) { LOGE("nativeRunBundleAndSnapshotFromLibrary GetArrayString error"); return nullptr; } - std::shared_ptr napi_facade = std::make_shared(env); + std::shared_ptr napi_facade = + std::make_shared(env); napi_create_reference(env, args[5], 1, &(napi_facade->ref_napi_obj_)); auto spawned_shell_holder = OHOS_SHELL_HOLDER->Spawn( @@ -654,7 +660,9 @@ napi_value PlatformViewOHOSNapi::nativeSpawn(napi_env env, napi_callback_info in } napi_value shell_holder_id; - napi_create_int64(env, reinterpret_cast(spawned_shell_holder.release()), &shell_holder_id); + napi_create_int64(env, + reinterpret_cast(spawned_shell_holder.release()), + &shell_holder_id); return shell_holder_id; } @@ -1448,9 +1456,8 @@ napi_value PlatformViewOHOSNapi::nativeGetSystemLanguages( napi_value PlatformViewOHOSNapi::nativeInitNativeImage( napi_env env, - napi_callback_info info) -{ - FML_DLOG(INFO)<<"PlatformViewOHOSNapi::nativeInitNativeImage"; + napi_callback_info info) { + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeInitNativeImage"; size_t argc = 3; napi_value args[3] = {nullptr}; int64_t shell_holder; @@ -1458,17 +1465,17 @@ napi_value PlatformViewOHOSNapi::nativeInitNativeImage( NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); NAPI_CALL(env, napi_get_value_int64(env, args[0], &shell_holder)); NAPI_CALL(env, napi_get_value_int64(env, args[1], &textureId)); - ImageNative *imageNative = OH_Image_InitImageNative(env, args[2]); + ImageNative* imageNative = OH_Image_InitImageNative(env, args[2]); // std::unique_ptr uImage; - OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTextureByImage(textureId, imageNative); + OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTextureByImage( + textureId, imageNative); return nullptr; } napi_value PlatformViewOHOSNapi::nativeRegisterTexture( napi_env env, - napi_callback_info info) -{ - FML_DLOG(INFO)<<"PlatformViewOHOSNapi::nativeRegisterTexture"; + napi_callback_info info) { + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeRegisterTexture"; size_t argc = 2; napi_value args[2] = {nullptr}; int64_t shell_holder; @@ -1476,7 +1483,8 @@ napi_value PlatformViewOHOSNapi::nativeRegisterTexture( NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); NAPI_CALL(env, napi_get_value_int64(env, args[0], &shell_holder)); NAPI_CALL(env, napi_get_value_int64(env, args[1], &textureId)); - int64_t surfaceId = OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTexture(textureId); + int64_t surfaceId = + OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTexture(textureId); napi_value res; napi_create_int64(env, surfaceId, &res); return res; @@ -1503,10 +1511,9 @@ napi_value PlatformViewOHOSNapi::nativeSetTextureBufferSize( } napi_value PlatformViewOHOSNapi::nativeUnregisterTexture( - napi_env env, - napi_callback_info info) -{ - FML_DLOG(INFO)<<"PlatformViewOHOSNapi::nativeUnregisterTexture"; + napi_env env, + napi_callback_info info) { + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeUnregisterTexture"; size_t argc = 2; napi_value args[2] = {nullptr}; int64_t shell_holder; @@ -1519,9 +1526,8 @@ napi_value PlatformViewOHOSNapi::nativeUnregisterTexture( } napi_value PlatformViewOHOSNapi::nativeMarkTextureFrameAvailable( - napi_env env, - napi_callback_info info) -{ + napi_env env, + napi_callback_info info) { size_t argc = 2; napi_value args[2] = {nullptr}; int64_t shell_holder; @@ -1534,10 +1540,9 @@ napi_value PlatformViewOHOSNapi::nativeMarkTextureFrameAvailable( } napi_value PlatformViewOHOSNapi::nativeRegisterPixelMap( - napi_env env, - napi_callback_info info) -{ - FML_DLOG(INFO)<<"PlatformViewOHOSNapi::nativeRegisterPixelMap"; + napi_env env, + napi_callback_info info) { + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeRegisterPixelMap"; size_t argc = 3; napi_value args[3] = {nullptr}; int64_t shell_holder; @@ -1545,16 +1550,16 @@ napi_value PlatformViewOHOSNapi::nativeRegisterPixelMap( NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); NAPI_CALL(env, napi_get_value_int64(env, args[0], &shell_holder)); NAPI_CALL(env, napi_get_value_int64(env, args[1], &textureId)); - NativePixelMap *nativePixelMap = OH_PixelMap_InitNativePixelMap(env, args[2]); - OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTextureByPixelMap(textureId, nativePixelMap); + NativePixelMap* nativePixelMap = OH_PixelMap_InitNativePixelMap(env, args[2]); + OHOS_SHELL_HOLDER->GetPlatformView()->RegisterExternalTextureByPixelMap( + textureId, nativePixelMap); return nullptr; } napi_value PlatformViewOHOSNapi::nativeSetTextureBackGroundPixelMap( - napi_env env, - napi_callback_info info) -{ - FML_DLOG(INFO)<<"PlatformViewOHOSNapi::nativeSetTextureBackGroundPixelMap"; + napi_env env, + napi_callback_info info) { + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetTextureBackGroundPixelMap"; size_t argc = 3; napi_value args[3] = {nullptr}; int64_t shell_holder; @@ -1562,8 +1567,9 @@ napi_value PlatformViewOHOSNapi::nativeSetTextureBackGroundPixelMap( NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); NAPI_CALL(env, napi_get_value_int64(env, args[0], &shell_holder)); NAPI_CALL(env, napi_get_value_int64(env, args[1], &textureId)); - NativePixelMap *nativePixelMap = OH_PixelMap_InitNativePixelMap(env, args[2]); - OHOS_SHELL_HOLDER->GetPlatformView()->SetExternalTextureBackGroundPixelMap(textureId, nativePixelMap); + NativePixelMap* nativePixelMap = OH_PixelMap_InitNativePixelMap(env, args[2]); + OHOS_SHELL_HOLDER->GetPlatformView()->SetExternalTextureBackGroundPixelMap( + textureId, nativePixelMap); return nullptr; } @@ -1598,36 +1604,41 @@ void PlatformViewOHOSNapi::SetPlatformTaskRunner( */ napi_value PlatformViewOHOSNapi::nativeXComponentAttachFlutterEngine( napi_env env, - napi_callback_info info){ - napi_status ret; - size_t argc = 2; - napi_value args[2] = {nullptr}; - std::string xcomponent_id; - int64_t shell_holder; - ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - if (ret != napi_ok) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine napi_get_cb_info error:" - << ret; - return nullptr; - } + napi_callback_info info) { + napi_status ret; + size_t argc = 2; + napi_value args[2] = {nullptr}; + std::string xcomponent_id; + int64_t shell_holder; + ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + if (ret != napi_ok) { + FML_DLOG(ERROR) + << "nativeXComponentAttachFlutterEngine napi_get_cb_info error:" << ret; + return nullptr; + } - if (fml::napi::GetString(env, args[0], xcomponent_id) != 0) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine xcomponent_id GetString error"; - return nullptr; - } + if (fml::napi::GetString(env, args[0], xcomponent_id) != 0) { + FML_DLOG(ERROR) + << "nativeXComponentAttachFlutterEngine xcomponent_id GetString error"; + return nullptr; + } - ret = napi_get_value_int64(env, args[1], &shell_holder); - if (ret != napi_ok) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine shell_holder napi_get_value_int64 error"; - return nullptr; - } - std::string shell_holder_str = std::to_string(shell_holder); + ret = napi_get_value_int64(env, args[1], &shell_holder); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine shell_holder " + "napi_get_value_int64 error"; + return nullptr; + } + std::string shell_holder_str = std::to_string(shell_holder); - LOGD("nativeXComponentAttachFlutterEngine xcomponent_id: %{public}s, shell_holder: %{public}ld ", - xcomponent_id.c_str(), shell_holder); + LOGD( + "nativeXComponentAttachFlutterEngine xcomponent_id: %{public}s, " + "shell_holder: %{public}ld ", + xcomponent_id.c_str(), shell_holder); - XComponentAdapter::GetInstance()->AttachFlutterEngine(xcomponent_id, shell_holder_str); - return nullptr; + XComponentAdapter::GetInstance()->AttachFlutterEngine(xcomponent_id, + shell_holder_str); + return nullptr; } /** * @brief xcomponent解除flutter引擎绑定 @@ -1638,31 +1649,34 @@ napi_value PlatformViewOHOSNapi::nativeXComponentAttachFlutterEngine( */ napi_value PlatformViewOHOSNapi::nativeXComponentDetachFlutterEngine( napi_env env, - napi_callback_info info){ - napi_status ret; - size_t argc = 2; - napi_value args[2] = {nullptr}; - std::string xcomponent_id; - int64_t shell_holder; - ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - if (ret != napi_ok) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine napi_get_cb_info error:" - << ret; - return nullptr; - } - if (fml::napi::GetString(env, args[0], xcomponent_id) != 0) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine xcomponent_id GetString error"; - return nullptr; - } - ret = napi_get_value_int64(env, args[1], &shell_holder); - if (ret != napi_ok) { - FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine shell_holder napi_get_value_int64 error"; - return nullptr; - } - - LOGD("nativeXComponentDetachFlutterEngine xcomponent_id: %{public}s", xcomponent_id.c_str()); - XComponentAdapter::GetInstance()->DetachFlutterEngine(xcomponent_id); + napi_callback_info info) { + napi_status ret; + size_t argc = 2; + napi_value args[2] = {nullptr}; + std::string xcomponent_id; + int64_t shell_holder; + ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + if (ret != napi_ok) { + FML_DLOG(ERROR) + << "nativeXComponentAttachFlutterEngine napi_get_cb_info error:" << ret; + return nullptr; + } + if (fml::napi::GetString(env, args[0], xcomponent_id) != 0) { + FML_DLOG(ERROR) + << "nativeXComponentAttachFlutterEngine xcomponent_id GetString error"; + return nullptr; + } + ret = napi_get_value_int64(env, args[1], &shell_holder); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "nativeXComponentAttachFlutterEngine shell_holder " + "napi_get_value_int64 error"; return nullptr; + } + + LOGD("nativeXComponentDetachFlutterEngine xcomponent_id: %{public}s", + xcomponent_id.c_str()); + XComponentAdapter::GetInstance()->DetachFlutterEngine(xcomponent_id); + return nullptr; } /** @@ -1678,74 +1692,82 @@ napi_value PlatformViewOHOSNapi::nativeXComponentDetachFlutterEngine( * @param timestamp: number * @return napi_value */ -napi_value PlatformViewOHOSNapi::nativeXComponentDispatchMouseWheel(napi_env env, napi_callback_info info) -{ - napi_status ret; - size_t argc = 8; - napi_value args[8] = {nullptr}; - int64_t shellHolder; - std::string xcomponentId; - std::string eventType; - int64_t fingerId; - double globalX; - double globalY; - double offsetY; - int64_t timestamp; - ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - if (ret != napi_ok) { - FML_DLOG(ERROR) << "nativeXComponentDispatchMouseWheel napi_get_cb_info error:" - << ret; - return nullptr; - } - ret = napi_get_value_int64(env, args[0], &shellHolder); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel shellHolder napi_get_value_int64 error"); - return nullptr; - } - if (fml::napi::GetString(env, args[1], xcomponentId) != 0) { - FML_DLOG(ERROR) << "nativeXComponentDispatchMouseWheel xcomponentId GetString error"; - return nullptr; - } - if (fml::napi::GetString(env, args[2], eventType) != 0) { - FML_DLOG(ERROR) << "nativeXComponentDispatchMouseWheel eventType GetString error"; - return nullptr; - } - ret = napi_get_value_int64(env, args[3], &fingerId); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel fingerId napi_get_value_int64 error"); - return nullptr; - } - ret = napi_get_value_double(env, args[4], &globalX); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel globalX napi_get_value_double error"); - return nullptr; - } - ret = napi_get_value_double(env, args[5], &globalY); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel globalY napi_get_value_double error"); - return nullptr; - } - ret = napi_get_value_double(env, args[6], &offsetY); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel offsetY napi_get_value_double error"); - return nullptr; - } - ret = napi_get_value_int64(env, args[7], ×tamp); - if (ret != napi_ok) { - LOGE("nativeXComponentDispatchMouseWheel timestamp napi_get_value_int64 error"); - return nullptr; - } - flutter::mouseWheelEvent event { - eventType, - shellHolder, - fingerId, - globalX, - globalY, - offsetY, - timestamp - }; - XComponentAdapter::GetInstance()->OnMouseWheel(xcomponentId, event); +napi_value PlatformViewOHOSNapi::nativeXComponentDispatchMouseWheel( + napi_env env, + napi_callback_info info) { + napi_status ret; + size_t argc = 8; + napi_value args[8] = {nullptr}; + int64_t shellHolder; + std::string xcomponentId; + std::string eventType; + int64_t fingerId; + double globalX; + double globalY; + double offsetY; + int64_t timestamp; + ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + if (ret != napi_ok) { + FML_DLOG(ERROR) + << "nativeXComponentDispatchMouseWheel napi_get_cb_info error:" << ret; + return nullptr; + } + ret = napi_get_value_int64(env, args[0], &shellHolder); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel shellHolder napi_get_value_int64 " + "error"); + return nullptr; + } + if (fml::napi::GetString(env, args[1], xcomponentId) != 0) { + FML_DLOG(ERROR) + << "nativeXComponentDispatchMouseWheel xcomponentId GetString error"; + return nullptr; + } + if (fml::napi::GetString(env, args[2], eventType) != 0) { + FML_DLOG(ERROR) + << "nativeXComponentDispatchMouseWheel eventType GetString error"; + return nullptr; + } + ret = napi_get_value_int64(env, args[3], &fingerId); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel fingerId napi_get_value_int64 " + "error"); + return nullptr; + } + ret = napi_get_value_double(env, args[4], &globalX); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel globalX napi_get_value_double " + "error"); + return nullptr; + } + ret = napi_get_value_double(env, args[5], &globalY); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel globalY napi_get_value_double " + "error"); + return nullptr; + } + ret = napi_get_value_double(env, args[6], &offsetY); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel offsetY napi_get_value_double " + "error"); + return nullptr; + } + ret = napi_get_value_int64(env, args[7], ×tamp); + if (ret != napi_ok) { + LOGE( + "nativeXComponentDispatchMouseWheel timestamp napi_get_value_int64 " + "error"); return nullptr; + } + flutter::mouseWheelEvent event{eventType, shellHolder, fingerId, globalX, + globalY, offsetY, timestamp}; + XComponentAdapter::GetInstance()->OnMouseWheel(xcomponentId, event); + return nullptr; } /** @@ -1754,27 +1776,29 @@ napi_value PlatformViewOHOSNapi::nativeXComponentDispatchMouseWheel(napi_env env * @param str: string * @return napi_value */ -napi_value PlatformViewOHOSNapi::nativeEncodeUtf8(napi_env env, napi_callback_info info) -{ - size_t argc = 1; - napi_value args[1] = {nullptr}; - napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); +napi_value PlatformViewOHOSNapi::nativeEncodeUtf8(napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - size_t length = 0; - napi_get_value_string_utf8(env, args[0], nullptr, 0, &length); + size_t length = 0; + napi_get_value_string_utf8(env, args[0], nullptr, 0, &length); - auto null_terminated_length = length + 1; - auto char_array = std::make_unique(null_terminated_length); - napi_get_value_string_utf8(env, args[0], char_array.get(), null_terminated_length, nullptr); + auto null_terminated_length = length + 1; + auto char_array = std::make_unique(null_terminated_length); + napi_get_value_string_utf8(env, args[0], char_array.get(), + null_terminated_length, nullptr); - void *data; - napi_value arraybuffer; - napi_create_arraybuffer(env, length, &data, &arraybuffer); - std::memcpy(data, char_array.get(), length); + void* data; + napi_value arraybuffer; + napi_create_arraybuffer(env, length, &data, &arraybuffer); + std::memcpy(data, char_array.get(), length); - napi_value uint8_array; - napi_create_typedarray(env, napi_uint8_array, length, arraybuffer, 0, &uint8_array); - return uint8_array; + napi_value uint8_array; + napi_create_typedarray(env, napi_uint8_array, length, arraybuffer, 0, + &uint8_array); + return uint8_array; } /** @@ -1783,19 +1807,185 @@ napi_value PlatformViewOHOSNapi::nativeEncodeUtf8(napi_env env, napi_callback_in * @param array: Uint8Array * @return napi_value */ -napi_value PlatformViewOHOSNapi::nativeDecodeUtf8(napi_env env, napi_callback_info info) -{ - size_t argc = 1; - napi_value args[1] = {nullptr}; - napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); +napi_value PlatformViewOHOSNapi::nativeDecodeUtf8(napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - size_t size = 0; - void *data = nullptr; - napi_get_typedarray_info(env, args[0], nullptr, &size, &data, nullptr, nullptr); + size_t size = 0; + void* data = nullptr; + napi_get_typedarray_info(env, args[0], nullptr, &size, &data, nullptr, + nullptr); - napi_value result; - napi_create_string_utf8(env, static_cast(data), size, &result); - return result; + napi_value result; + napi_create_string_utf8(env, static_cast(data), size, &result); + return result; +} + +napi_value PlatformViewOHOSNapi::nativeUpdateSemantics( + napi_env env, + napi_callback_info info) { + + return nullptr; +} + +napi_value PlatformViewOHOSNapi::nativeUpdateCustomAccessibilityActions( + napi_env env, + napi_callback_info info) { + + return nullptr; +} +/** + * 监听获取系统的无障碍服务是否开启 + */ +napi_value PlatformViewOHOSNapi::nativeAccessibilityStateChange( + napi_env env, + napi_callback_info info) { + napi_status ret; + size_t argc = 1; + napi_value args[1] = {nullptr}; + ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeAccessibilityStateChange " + "napi_get_cb_info error:" + << ret; + return nullptr; + } + bool state = false; + ret = napi_get_value_bool(env, args[0], &state); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeAccessibilityStateChange " + "napi_get_value_bool error:" + << ret; + return nullptr; + } + LOGD( + "PlatformViewOHOSNapi::nativeAccessibilityStateChange state is: " + "%{public}s", + (state ? "true" : "false")); + // 传递到无障碍管理类 + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->isOhosAccessibilityEnabled_ = state; + + return nullptr; +} + +napi_value PlatformViewOHOSNapi::nativeAnnounce( + napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + size_t length = 0; + napi_get_value_string_utf8(env, args[0], nullptr, 0, &length); + + auto null_terminated_length = length + 1; + auto char_array = std::make_unique(null_terminated_length); + napi_get_value_string_utf8(env, args[0], char_array.get(), + null_terminated_length, nullptr); + LOGD("PlatformViewOHOSNapi::nativeAnnounce message: %{public}s", char_array.get()); + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->announce(char_array); + return nullptr; +} + +napi_value PlatformViewOHOSNapi::nativeSetSemanticsEnabled(napi_env env, napi_callback_info info) { + napi_status ret; + size_t argc = 2; + napi_value args[2] = {nullptr}; + ret = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled " + "napi_get_cb_info error:" + << ret; + return nullptr; + } + + int64_t shell_holder; + ret = napi_get_value_int64(env, args[0], &shell_holder); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled " + "napi_get_value_int64 error:" + << ret; + return nullptr; + } + bool enabled = false; + ret = napi_get_value_bool(env, args[1], &enabled); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled " + "napi_get_value_bool error:" + << ret; + return nullptr; + } + OHOS_SHELL_HOLDER->GetPlatformView()->SetSemanticsEnabled(enabled); + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled " + "OHOS_SHELL_HOLDER->GetPlatformView()->SetSemanticsEnabled= "<ClearFlutterSemanticsCaches(); + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled -> ClearFlutterSemanticsCaches()"; + } + + //给无障碍bridge传递nativeShellHolderId + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->nativeShellHolder_ = shell_holder; + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled -> shell_holder:"<SetBoldText(fontWeightScale, shell_holder); + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetFontWeightScale -> shell_holder: " + << shell_holder + << " fontWeightScale: "<< fontWeightScale; + return nullptr; +} + +napi_value PlatformViewOHOSNapi::nativeGetShellHolderId(napi_env env, napi_callback_info info) { + napi_status ret; + size_t argc = 1; + napi_value args[1] = {nullptr}; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + int64_t shell_holder; + ret = napi_get_value_int64(env, args[0], &shell_holder); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled " + "napi_get_value_int64 error:" + << ret; + return nullptr; + } + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->nativeShellHolder_ = shell_holder; + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeGetShellHolderId -> shell_holder:"<IS_FLUTTER_NAVIGATE = isNavigate; + FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeGetFlutterNavigationAction -> "< touchPacketString, int size); + void FlutterViewOnTouchEvent(std::shared_ptr touchPacketString, + int size); static napi_value nativeUpdateRefreshRate( napi_env env, @@ -155,29 +159,23 @@ class PlatformViewOHOSNapi { napi_env env, napi_callback_info info); // 应用下发系统语言设置 - static napi_value nativeInitNativeImage( - napi_env env, - napi_callback_info info); + static napi_value nativeInitNativeImage(napi_env env, + napi_callback_info info); - static napi_value nativeUnregisterTexture( - napi_env env, - napi_callback_info info); + static napi_value nativeUnregisterTexture(napi_env env, + napi_callback_info info); - static napi_value nativeMarkTextureFrameAvailable( - napi_env env, - napi_callback_info info); + static napi_value nativeMarkTextureFrameAvailable(napi_env env, + napi_callback_info info); - static napi_value nativeRegisterPixelMap( - napi_env env, - napi_callback_info info); + static napi_value nativeRegisterPixelMap(napi_env env, + napi_callback_info info); - static napi_value nativeSetTextureBackGroundPixelMap( - napi_env env, - napi_callback_info info); + static napi_value nativeSetTextureBackGroundPixelMap(napi_env env, + napi_callback_info info); - static napi_value nativeRegisterTexture( - napi_env env, - napi_callback_info info); + static napi_value nativeRegisterTexture(napi_env env, + napi_callback_info info); static napi_value nativeSetTextureBufferSize( napi_env env, @@ -186,31 +184,52 @@ class PlatformViewOHOSNapi { // Surface相关,XComponent调用 static void SurfaceCreated(int64_t shell_holder, void* window); - static void SurfaceChanged( - int64_t shell_holder, - int32_t width, - int32_t height); + static void SurfaceChanged(int64_t shell_holder, + int32_t width, + int32_t height); static void SurfaceDestroyed(int64_t shell_holder); - static int64_t GetShellHolder(); + static napi_value nativeXComponentAttachFlutterEngine( napi_env env, napi_callback_info info); static napi_value nativeXComponentDetachFlutterEngine( napi_env env, napi_callback_info info); - static napi_value nativeXComponentDispatchMouseWheel( + static napi_value nativeXComponentDispatchMouseWheel(napi_env env, + napi_callback_info info); + static napi_value nativeEncodeUtf8(napi_env env, napi_callback_info info); + static napi_value nativeDecodeUtf8(napi_env env, napi_callback_info info); + + /** + * ets call c++ + */ + static napi_value nativeUpdateSemantics(napi_env env, + napi_callback_info info); + static napi_value nativeUpdateCustomAccessibilityActions( + napi_env env, + napi_callback_info info); + static napi_value nativeAccessibilityStateChange( napi_env env, napi_callback_info info); - static napi_value nativeEncodeUtf8( + static napi_value nativeAnnounce( napi_env env, napi_callback_info info); - static napi_value nativeDecodeUtf8( + static napi_value nativeSetSemanticsEnabled( + napi_env env, + napi_callback_info info); + static napi_value nativeSetFontWeightScale( + napi_env env, + napi_callback_info info); + static napi_value nativeGetShellHolderId( napi_env env, napi_callback_info info); static napi_value nativeLookupCallbackInformation( napi_env env, napi_callback_info info); + static napi_value nativeGetFlutterNavigationAction( + napi_env env, + napi_callback_info info); private: static napi_env env_; diff --git a/shell/platform/ohos/ohos_xcomponent_adapter.cpp b/shell/platform/ohos/ohos_xcomponent_adapter.cpp index 562e17f44b8a24286b035dbc9e8d2f0f3f6e9895..fa3254d0a483c4b0ddee793ba2ac0b390fcc7809 100644 --- a/shell/platform/ohos/ohos_xcomponent_adapter.cpp +++ b/shell/platform/ohos/ohos_xcomponent_adapter.cpp @@ -21,6 +21,7 @@ namespace flutter { + bool g_isMouseLeftActive = false; double g_scrollDistance = 0.0; double g_resizeRate = 0.8; @@ -206,6 +207,7 @@ void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) { for(auto it: XComponentAdapter::GetInstance()->xcomponetMap_) { if(it.second->nativeXComponent_ == component) { + LOGD("OnSurfaceCreatedCB is called"); it.second->OnSurfaceCreated(component, window); } } @@ -266,6 +268,101 @@ void XComponentBase::BindXComponentCallback() { mouseCallback_.DispatchHoverEvent = DispatchHoverEventCB; } + +/** Called when need to get element infos based on a specified node. */ +int32_t FindAccessibilityNodeInfosById( + int64_t elementId, + ArkUI_AccessibilitySearchMode mode, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->FindAccessibilityNodeInfosById(elementId, mode, requestId, elementList); + LOGD("accessibilityProviderCallback_.FindAccessibilityNodeInfosById"); + return 0; +} + +/** Called when need to get element infos based on a specified node and text content. */ +int32_t FindAccessibilityNodeInfosByText( + int64_t elementId, + const char* text, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->FindAccessibilityNodeInfosByText(elementId, text, requestId, elementList); + LOGD("accessibilityProviderCallback_.FindAccessibilityNodeInfosByText"); + return 0; +} + +/** Called when need to get the focused element info based on a specified node. */ +int32_t FindFocusedAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusType focusType, + int32_t requestId, + ArkUI_AccessibilityElementInfo* elementinfo) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->FindFocusedAccessibilityNode(elementId, focusType, requestId, elementinfo); + LOGD("accessibilityProviderCallback_.FindFocusedAccessibilityNode"); + return 0; +} + +/** Query the node that can be focused based on the reference node. Query the next node that can be focused based on the mode and direction. */ +int32_t FindNextFocusAccessibilityNode( + int64_t elementId, + ArkUI_AccessibilityFocusMoveDirection direction, + int32_t requestId, + ArkUI_AccessibilityElementInfo *elementList) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->FindNextFocusAccessibilityNode(elementId, direction, requestId, elementList); + LOGD("accessibilityProviderCallback_.FindNextFocusAccessibilityNode"); + return 0; +} + +/** Performing the Action operation on a specified node. */ +int32_t ExecuteAccessibilityAction( + int64_t elementId, + ArkUI_Accessibility_ActionType action, + ArkUI_AccessibilityActionArguments* actionArguments, + int32_t requestId) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->ExecuteAccessibilityAction(elementId, action, actionArguments, requestId); + LOGD("accessibilityProviderCallback_.ExecuteAccessibilityAction"); + return 0; +} + +/** Clears the focus status of the currently focused node */ +int32_t ClearFocusedFocusAccessibilityNode() +{ + LOGD("accessibilityProviderCallback_.ClearFocusedFocusAccessibilityNode"); + return 0; +} + +/** Queries the current cursor position of a specified node. */ +int32_t GetAccessibilityNodeCursorPosition( + int64_t elementId, + int32_t requestId, + int32_t* index) +{ + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->GetAccessibilityNodeCursorPosition(elementId, requestId, index); + LOGD("accessibilityProviderCallback_.GetAccessibilityNodeCursorPosition"); + return 0; +} + +void XComponentBase::BindAccessibilityProviderCallback() { + accessibilityProviderCallback_.findAccessibilityNodeInfosById = FindAccessibilityNodeInfosById; + accessibilityProviderCallback_.findAccessibilityNodeInfosByText = FindAccessibilityNodeInfosByText; + accessibilityProviderCallback_.findFocusedAccessibilityNode = FindFocusedAccessibilityNode; + accessibilityProviderCallback_.findNextFocusAccessibilityNode = FindNextFocusAccessibilityNode; + accessibilityProviderCallback_.executeAccessibilityAction = ExecuteAccessibilityAction; + accessibilityProviderCallback_.clearFocusedFocusAccessibilityNode = ClearFocusedFocusAccessibilityNode; + accessibilityProviderCallback_.getAccessibilityNodeCursorPosition = GetAccessibilityNodeCursorPosition; +} + XComponentBase::XComponentBase(std::string id){ id_ = id; isEngineAttached_ = false; @@ -307,6 +404,28 @@ void XComponentBase::SetNativeXComponent(OH_NativeXComponent* nativeXComponent){ BindXComponentCallback(); OH_NativeXComponent_RegisterCallback(nativeXComponent_, &callback_); OH_NativeXComponent_RegisterMouseEventCallback(nativeXComponent_, &mouseCallback_); + + BindAccessibilityProviderCallback(); + ArkUI_AccessibilityProvider* accessibilityProvider = nullptr; + int32_t ret1 = OH_NativeXComponent_GetNativeAccessibilityProvider( + nativeXComponent_, &accessibilityProvider); + if (ret1 != 0) { + LOGE("OH_NativeXComponent_GetNativeAccessibilityProvider is failed"); + return; + } + int32_t ret2 = OH_ArkUI_AccessibilityProviderRegisterCallback( + accessibilityProvider, &accessibilityProviderCallback_); + if (ret2 != 0) { + LOGE("OH_ArkUI_AccessibilityProviderRegisterCallback is failed"); + return; + } + LOGE("OH_ArkUI_AccessibilityProviderRegisterCallback is %{public}d", ret2); + + //将ArkUI_AccessibilityProvider传到无障碍bridge类 + auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); + ohosAccessibilityBridge->provider_ = accessibilityProvider; + + LOGI("XComponentBase::SetNativeXComponent OH_ArkUI_AccessibilityProviderRegisterCallback is succeed"); } } diff --git a/shell/platform/ohos/ohos_xcomponent_adapter.h b/shell/platform/ohos/ohos_xcomponent_adapter.h index aecea89ded2c10ff634eeaa30e292e56bc661730..d5accff83a5fb91983aad00a03f03979f4ae17d3 100644 --- a/shell/platform/ohos/ohos_xcomponent_adapter.h +++ b/shell/platform/ohos/ohos_xcomponent_adapter.h @@ -16,18 +16,21 @@ #ifndef OHOS_XCOMPONENT_ADAPTER_H #define OHOS_XCOMPONENT_ADAPTER_H #include +#include #include #include "flutter/shell/platform/ohos/ohos_touch_processor.h" #include "flutter/shell/platform/ohos/napi/platform_view_ohos_napi.h" #include "napi/native_api.h" #include "napi_common.h" #include +#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h" namespace flutter { class XComponentBase { private: void BindXComponentCallback(); + void BindAccessibilityProviderCallback(); public: XComponentBase(std::string id); @@ -48,6 +51,7 @@ public: OH_NativeXComponent_TouchEvent touchEvent_; OH_NativeXComponent_Callback callback_; OH_NativeXComponent_MouseEvent_Callback mouseCallback_; + ArkUI_AccessibilityProviderCallbacks accessibilityProviderCallback_; std::string id_; std::string shellholderId_; bool isEngineAttached_; diff --git a/shell/platform/ohos/platform_view_ohos.cpp b/shell/platform/ohos/platform_view_ohos.cpp index bad544f3f89e7b26751c3a892b4ce1b2c1c483f2..212c3274ecf324cefaaf2ee291dc2915b8a79caa 100644 --- a/shell/platform/ohos/platform_view_ohos.cpp +++ b/shell/platform/ohos/platform_view_ohos.cpp @@ -22,8 +22,9 @@ #include "flutter/shell/platform/ohos/ohos_surface_software.h" #include "flutter/shell/platform/ohos/platform_message_response_ohos.h" #include "napi_common.h" -#include "ohos_logging.h" #include "ohos_external_texture_gl.h" +#include "ohos_logging.h" +#include "flutter/shell/platform/ohos/platform_view_ohos_delegate.h" #include @@ -117,9 +118,11 @@ PlatformViewOHOS::PlatformViewOHOS( PlatformViewOHOS::~PlatformViewOHOS() { FML_LOG(INFO) << "PlatformViewOHOS::~PlatformViewOHOS"; - for (std::map::iterator it = contextDatas_.begin(); it != contextDatas_.end(); ++it) { + for (std::map::iterator it = contextDatas_.begin(); + it != contextDatas_.end(); ++it) { if (it->second != nullptr) { - OhosImageFrameData* data = reinterpret_cast(it->second); + OhosImageFrameData* data = + reinterpret_cast(it->second); delete data; data = nullptr; it->second = nullptr; @@ -202,8 +205,7 @@ void PlatformViewOHOS::SetDestroyed(bool isDestroyed) { } // |PlatformView| -void PlatformViewOHOS::NotifyDestroyed() -{ +void PlatformViewOHOS::NotifyDestroyed() { SetDestroyed(true); LOGI("PlatformViewOHOS NotifyDestroyed enter"); PlatformView::NotifyDestroyed(); @@ -225,7 +227,7 @@ void PlatformViewOHOS::DispatchPlatformMessage(std::string name, void* message, int messageLenth, int reponseId) { - FML_DLOG(INFO) << "DispatchSemanticsAction(" << name << ",," << messageLenth + FML_DLOG(INFO) << "DispatchPlatformMessage(" << name << "," << messageLenth << "," << reponseId; fml::MallocMapping mapMessage = fml::MallocMapping::Copy(message, messageLenth); @@ -256,7 +258,7 @@ void PlatformViewOHOS::DispatchSemanticsAction(int id, int action, void* actionData, int actionDataLenth) { - FML_DLOG(INFO) << "DispatchSemanticsAction(" << id << "," << action << "," + FML_DLOG(INFO) << "DispatchSemanticsAction -> id=" << id << ", action=" << action << ", actionDataLenth" << actionDataLenth; auto args_vector = fml::MallocMapping::Copy(actionData, actionDataLenth); @@ -293,11 +295,13 @@ void PlatformViewOHOS::UpdateAssetResolverByType( delegate_.UpdateAssetResolverByType(std::move(updated_asset_resolver), type); } -// todo +// ohos_accessbility_bridge void PlatformViewOHOS::UpdateSemantics( flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { - FML_DLOG(INFO) << "UpdateSemantics"; + FML_DLOG(INFO) << "PlatformViewOHOS::UpdateSemantics is called"; + auto ax_bridge_delegate_ = OhosAccessibilityBridge::GetInstance(); + ax_bridge_delegate_->updateSemantics(update, actions); } // |PlatformView| @@ -424,17 +428,15 @@ void PlatformViewOHOS::FireFirstFrameCallback() { napi_facade_->FlutterViewOnFirstFrame(); } -void PlatformViewOHOS::RegisterExternalTextureByImage( - int64_t texture_id, - ImageNative* image) -{ +void PlatformViewOHOS::RegisterExternalTextureByImage(int64_t texture_id, + ImageNative* image) { if (ohos_context_->RenderingApi() == OHOSRenderingAPI::kOpenGLES) { auto iter = external_texture_gl_.find(texture_id); if (iter != external_texture_gl_.end()) { iter->second->DispatchImage(image); } else { std::shared_ptr ohos_external_gl = - std::make_shared(texture_id, ohos_surface_); + std::make_shared(texture_id, ohos_surface_); external_texture_gl_[texture_id] = ohos_external_gl; RegisterTexture(ohos_external_gl); ohos_external_gl->DispatchImage(image); @@ -448,14 +450,14 @@ PointerDataDispatcherMaker PlatformViewOHOS::GetDispatcherMaker() { }; } -uint64_t PlatformViewOHOS::RegisterExternalTexture(int64_t texture_id) -{ +uint64_t PlatformViewOHOS::RegisterExternalTexture(int64_t texture_id) { uint64_t surface_id = 0; int ret = -1; if (ohos_context_->RenderingApi() == OHOSRenderingAPI::kOpenGLES) { std::shared_ptr ohos_external_gl = - std::make_shared(texture_id, ohos_surface_); - ohos_external_gl->nativeImage_ = OH_NativeImage_Create(texture_id, GL_TEXTURE_EXTERNAL_OES); + std::make_shared(texture_id, ohos_surface_); + ohos_external_gl->nativeImage_ = + OH_NativeImage_Create(texture_id, GL_TEXTURE_EXTERNAL_OES); if (ohos_external_gl->nativeImage_ == nullptr) { FML_DLOG(ERROR) << "Error with OH_NativeImage_Create"; return surface_id; @@ -465,12 +467,15 @@ uint64_t PlatformViewOHOS::RegisterExternalTexture(int64_t texture_id) OH_OnFrameAvailableListener listener; listener.context = contextData; listener.onFrameAvailable = &PlatformViewOHOS::OnNativeImageFrameAvailable; - ret = OH_NativeImage_SetOnFrameAvailableListener(ohos_external_gl->nativeImage_, listener); + ret = OH_NativeImage_SetOnFrameAvailableListener( + ohos_external_gl->nativeImage_, listener); if (ret != 0) { - FML_DLOG(ERROR) << "Error with OH_NativeImage_SetOnFrameAvailableListener"; + FML_DLOG(ERROR) + << "Error with OH_NativeImage_SetOnFrameAvailableListener"; return surface_id; } - ret = OH_NativeImage_GetSurfaceId(ohos_external_gl->nativeImage_, &surface_id); + ret = OH_NativeImage_GetSurfaceId(ohos_external_gl->nativeImage_, + &surface_id); ohos_external_gl->first_update_ = false; if (ret != 0) { FML_DLOG(ERROR) << "Error with OH_NativeImage_GetSurfaceId"; @@ -495,48 +500,49 @@ void PlatformViewOHOS::SetTextureBufferSize( } } -void PlatformViewOHOS::OnNativeImageFrameAvailable(void *data) -{ - auto frameData = reinterpret_cast(data); +void PlatformViewOHOS::OnNativeImageFrameAvailable(void* data) { + auto frameData = reinterpret_cast(data); if (frameData == nullptr || frameData->context_ == nullptr) { - FML_DLOG(ERROR) << "OnNativeImageFrameAvailable, frameData or context_ is null."; + FML_DLOG(ERROR) + << "OnNativeImageFrameAvailable, frameData or context_ is null."; return; } if (frameData->context_->GetDestroyed()) { - FML_LOG(ERROR) << "OnNativeImageFrameAvailable NotifyDstroyed, will not MarkTextureFrameAvailable"; + FML_LOG(ERROR) << "OnNativeImageFrameAvailable NotifyDstroyed, will not " + "MarkTextureFrameAvailable"; return; } - std::shared_ptr ohos_surface = frameData->context_->ohos_surface_; + std::shared_ptr ohos_surface = + frameData->context_->ohos_surface_; const TaskRunners task_runners = frameData->context_->task_runners_; if (ohos_surface) { fml::TaskRunner::RunNowOrPostTask( - task_runners.GetPlatformTaskRunner(), - [frameData]() { + task_runners.GetPlatformTaskRunner(), [frameData]() { if (frameData->context_->GetDestroyed()) { - FML_LOG(ERROR) << "OnNativeImageFrameAvailable NotifyDstroyed, will not MarkTextureFrameAvailable"; + FML_LOG(ERROR) << "OnNativeImageFrameAvailable NotifyDstroyed, " + "will not MarkTextureFrameAvailable"; return; } - frameData->context_->MarkTextureFrameAvailable(frameData->texture_id_); + frameData->context_->MarkTextureFrameAvailable( + frameData->texture_id_); }); } } -void PlatformViewOHOS::UnRegisterExternalTexture(int64_t texture_id) -{ - FML_DLOG(INFO) << "PlatformViewOHOS::UnRegisterExternalTexture, texture_id=" << texture_id; +void PlatformViewOHOS::UnRegisterExternalTexture(int64_t texture_id) { + FML_DLOG(INFO) << "PlatformViewOHOS::UnRegisterExternalTexture, texture_id=" + << texture_id; external_texture_gl_.erase(texture_id); UnregisterTexture(texture_id); std::map::iterator it = contextDatas_.find(texture_id); if (it != contextDatas_.end()) { if (it->second != nullptr) { - OhosImageFrameData* data = reinterpret_cast(it->second); + OhosImageFrameData* data = + reinterpret_cast(it->second); task_runners_.GetPlatformTaskRunner()->PostDelayedTask( - [data_ = data]() { - delete data_; - }, - fml::TimeDelta::FromSeconds(2)); + [data_ = data]() { delete data_; }, fml::TimeDelta::FromSeconds(2)); data = nullptr; it->second = nullptr; } @@ -544,8 +550,9 @@ void PlatformViewOHOS::UnRegisterExternalTexture(int64_t texture_id) } } -void PlatformViewOHOS::RegisterExternalTextureByPixelMap(int64_t texture_id, NativePixelMap* pixelMap) -{ +void PlatformViewOHOS::RegisterExternalTextureByPixelMap( + int64_t texture_id, + NativePixelMap* pixelMap) { if (ohos_context_->RenderingApi() == OHOSRenderingAPI::kOpenGLES) { auto iter = external_texture_gl_.find(texture_id); if (iter != external_texture_gl_.end()) { @@ -561,8 +568,9 @@ void PlatformViewOHOS::RegisterExternalTextureByPixelMap(int64_t texture_id, Nat } } -void PlatformViewOHOS::SetExternalTextureBackGroundPixelMap(int64_t texture_id, NativePixelMap* pixelMap) -{ +void PlatformViewOHOS::SetExternalTextureBackGroundPixelMap( + int64_t texture_id, + NativePixelMap* pixelMap) { if (ohos_context_->RenderingApi() == OHOSRenderingAPI::kOpenGLES) { auto iter = external_texture_gl_.find(texture_id); if (iter != external_texture_gl_.end()) { @@ -571,14 +579,14 @@ void PlatformViewOHOS::SetExternalTextureBackGroundPixelMap(int64_t texture_id, } } -void PlatformViewOHOS::OnTouchEvent(const std::shared_ptr touchPacketString, int size) -{ +void PlatformViewOHOS::OnTouchEvent( + const std::shared_ptr touchPacketString, + int size) { return napi_facade_->FlutterViewOnTouchEvent(touchPacketString, size); } -OhosImageFrameData::OhosImageFrameData( - PlatformViewOHOS* context, - int64_t texture_id) +OhosImageFrameData::OhosImageFrameData(PlatformViewOHOS* context, + int64_t texture_id) : context_(context), texture_id_(texture_id) {} OhosImageFrameData::~OhosImageFrameData() = default; diff --git a/shell/platform/ohos/platform_view_ohos.h b/shell/platform/ohos/platform_view_ohos.h index 14c60e884c745ece2c2043ecb4d63222d86d153f..dac7916885cb2bc574c4d6ee7c09b96be0cd4c2a 100644 --- a/shell/platform/ohos/platform_view_ohos.h +++ b/shell/platform/ohos/platform_view_ohos.h @@ -35,7 +35,8 @@ #include "flutter/shell/platform/ohos/surface/ohos_snapshot_surface_producer.h" #include "flutter/shell/platform/ohos/surface/ohos_surface.h" #include "flutter/shell/platform/ohos/vsync_waiter_ohos.h" - +#include "flutter/shell/platform/ohos/platform_view_ohos_delegate.h" +#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h" namespace flutter { class OhosSurfaceFactoryImpl : public OhosSurfaceFactory { @@ -141,6 +142,8 @@ class PlatformViewOHOS final : public PlatformView { std::map> external_texture_gl_; std::map contextDatas_; + std::shared_ptr platform_view_ohos_delegate_; + std::atomic isDestroyed_; bool GetDestroyed(); diff --git a/shell/platform/ohos/platform_view_ohos_delegate.cpp b/shell/platform/ohos/platform_view_ohos_delegate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab423233dc76315ff4bae7de22f59d09053f1250 --- /dev/null +++ b/shell/platform/ohos/platform_view_ohos_delegate.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 "flutter/shell/platform/ohos/platform_view_ohos_delegate.h" +#include + +namespace flutter { + +void putStringAttributesIntoBuffer( + const StringAttributes& attributes, + int32_t* buffer_int32, + size_t& position, + std::vector>& string_attribute_args) { + if (attributes.empty()) { + buffer_int32[position++] = -1; + return; + } + buffer_int32[position++] = attributes.size(); + for (const auto& attribute : attributes) { + buffer_int32[position++] = attribute->start; + buffer_int32[position++] = attribute->end; + buffer_int32[position++] = static_cast(attribute->type); + switch (attribute->type) { + case StringAttributeType::kSpellOut: + buffer_int32[position++] = -1; + break; + case StringAttributeType::kLocale: + buffer_int32[position++] = string_attribute_args.size(); + std::shared_ptr locale_attribute = + std::static_pointer_cast(attribute); + string_attribute_args.push_back( + {locale_attribute->locale.begin(), locale_attribute->locale.end()}); + break; + } + } +} +// interaction between java and native, encoding the semantics info to bytebuffer +PlatformViewOHOSDelegate::PlatformViewOHOSDelegate( + std::shared_ptr napi_facade) + : napi_facade_(std::move(napi_facade)) {}; + +void PlatformViewOHOSDelegate::UpdateSemantics( + const flutter::SemanticsNodeUpdates& update, + const flutter::CustomAccessibilityActionUpdates& actions) { + constexpr size_t kBytesPerNode = 47 * sizeof(int32_t); + constexpr size_t kBytesPerChild = sizeof(int32_t); + constexpr size_t kBytesPerCustomAction = sizeof(int32_t); + constexpr size_t kBytesPerAction = 4 * sizeof(int32_t); + constexpr size_t kBytesPerStringAttribute = 4 * sizeof(int32_t); + + { + size_t num_bytes = 0; + for (const auto& value : update) { + num_bytes += kBytesPerNode; + num_bytes += + value.second.childrenInTraversalOrder.size() * kBytesPerChild; + num_bytes += value.second.childrenInHitTestOrder.size() * kBytesPerChild; + num_bytes += value.second.customAccessibilityActions.size() * + kBytesPerCustomAction; + num_bytes += + value.second.labelAttributes.size() * kBytesPerStringAttribute; + num_bytes += + value.second.valueAttributes.size() * kBytesPerStringAttribute; + num_bytes += value.second.increasedValueAttributes.size() * + kBytesPerStringAttribute; + num_bytes += value.second.decreasedValueAttributes.size() * + kBytesPerStringAttribute; + num_bytes += + value.second.hintAttributes.size() * kBytesPerStringAttribute; + } + // encode the updated nodes/actions into bytebuffer/strings + std::vector buffer(num_bytes); + std::vector strings; + std::vector> string_attribute_args; + + int32_t* buffer_int32 = reinterpret_cast(&buffer[0]); + float* buffer_float32 = reinterpret_cast(&buffer[0]); + + size_t position = 0; + for (const auto& value : update) { + // make sure you update kBytesPerNode and/or kBytesPerChild above to + // match the number of values you are sending. + const flutter::SemanticsNode& node = value.second; + buffer_int32[position++] = node.id; + buffer_int32[position++] = node.flags; + buffer_int32[position++] = node.actions; + buffer_int32[position++] = node.maxValueLength; + buffer_int32[position++] = node.currentValueLength; + buffer_int32[position++] = node.textSelectionBase; + buffer_int32[position++] = node.textSelectionExtent; + buffer_int32[position++] = node.platformViewId; + buffer_int32[position++] = node.scrollChildren; + buffer_int32[position++] = node.scrollIndex; + buffer_float32[position++] = static_cast(node.scrollPosition); + buffer_float32[position++] = static_cast(node.scrollExtentMax); + buffer_float32[position++] = static_cast(node.scrollExtentMin); + if (node.label.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.label); + } + + putStringAttributesIntoBuffer(node.labelAttributes, buffer_int32, + position, string_attribute_args); + if (node.value.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.value); + } + + putStringAttributesIntoBuffer(node.valueAttributes, buffer_int32, + position, string_attribute_args); + if (node.increasedValue.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.increasedValue); + } + + putStringAttributesIntoBuffer(node.increasedValueAttributes, buffer_int32, + position, string_attribute_args); + if (node.decreasedValue.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.decreasedValue); + } + + putStringAttributesIntoBuffer(node.decreasedValueAttributes, buffer_int32, + position, string_attribute_args); + + if (node.hint.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.hint); + } + + putStringAttributesIntoBuffer(node.hintAttributes, buffer_int32, position, + string_attribute_args); + + if (node.tooltip.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.tooltip); + } + + buffer_int32[position++] = node.textDirection; + buffer_float32[position++] = node.rect.left(); + buffer_float32[position++] = node.rect.top(); + buffer_float32[position++] = node.rect.right(); + buffer_float32[position++] = node.rect.bottom(); + node.transform.getColMajor(&buffer_float32[position]); + position += 16; + + buffer_int32[position++] = node.childrenInTraversalOrder.size(); + for (int32_t child : node.childrenInTraversalOrder) { + buffer_int32[position++] = child; + } + + for (int32_t child : node.childrenInHitTestOrder) { + buffer_int32[position++] = child; + } + + buffer_int32[position++] = node.customAccessibilityActions.size(); + for (int32_t child : node.customAccessibilityActions) { + buffer_int32[position++] = child; + } + } + + // custom accessibility actions. + size_t num_action_bytes = actions.size() * kBytesPerAction; + std::vector actions_buffer(num_action_bytes); + int32_t* actions_buffer_int32 = + reinterpret_cast(&actions_buffer[0]); + + std::vector action_strings; + size_t actions_position = 0; + for (const auto& value : actions) { + // If you edit this code, make sure you update kBytesPerAction + // to match the number of values you are sending. + const flutter::CustomAccessibilityAction& action = value.second; + actions_buffer_int32[actions_position++] = action.id; + actions_buffer_int32[actions_position++] = action.overrideId; + if (action.label.empty()) { + actions_buffer_int32[actions_position++] = -1; + } else { + actions_buffer_int32[actions_position++] = action_strings.size(); + action_strings.push_back(action.label); + } + if (action.hint.empty()) { + actions_buffer_int32[actions_position++] = -1; + } else { + actions_buffer_int32[actions_position++] = action_strings.size(); + action_strings.push_back(action.hint); + } + } + + if (!actions_buffer.empty()) { + FML_DLOG(INFO) << "PlatformViewOHOSDelegate::" + "updateCustomAccessibilityActions is called"; + } + + if (!buffer.empty()) { + FML_DLOG(INFO) << "PlatformViewOHOSDelegate::UpdateSemantics is called"; + } + } +} + +} // namespace flutter \ No newline at end of file diff --git a/shell/platform/ohos/platform_view_ohos_delegate.h b/shell/platform/ohos/platform_view_ohos_delegate.h new file mode 100644 index 0000000000000000000000000000000000000000..5b5fa634f01a7a025b2e85c5b32e27dbc19d0583 --- /dev/null +++ b/shell/platform/ohos/platform_view_ohos_delegate.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. + */ + +#ifndef SHELL_PLATFORM_OHOS_PLATFORM_VIEW_OHOS_DELEGATE +#define SHELL_PLATFORM_OHOS_PLATFORM_VIEW_OHOS_DELEGATE + +#include +#include +#include + +#include "flutter/shell/common/platform_view.h" +#include "flutter/shell/platform/ohos/napi/platform_view_ohos_napi.h" +#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h" + +namespace flutter { + +class PlatformViewOHOSDelegate { + public: + explicit PlatformViewOHOSDelegate( + std::shared_ptr napi_facade); + + void UpdateSemantics( + const flutter::SemanticsNodeUpdates& update, + const flutter::CustomAccessibilityActionUpdates& actions); + + private: + const std::shared_ptr napi_facade_; + +}; +} // namespace flutter +#endif \ No newline at end of file