diff --git a/shell/platform/ohos/BUILD.gn b/shell/platform/ohos/BUILD.gn index 68bd4bca43f047d8e0480864a847a748f748446e..5adde6edeb7fc3353fc54ef2edbc19f45dd6fb73 100644 --- a/shell/platform/ohos/BUILD.gn +++ b/shell/platform/ohos/BUILD.gn @@ -88,6 +88,7 @@ source_set("flutter_ohos_sources") { "./accessibility/ohos_accessibility_bridge.h", "./accessibility/ohos_accessibility_features.h", "./accessibility/ohos_accessibility_manager.h", + "./accessibility/native_accessibility_channel.h" ] #configs += [ "//flutter/shell/platform/ohos/config:gtk" ] @@ -122,6 +123,7 @@ source_set("flutter_ohos_sources") { "./accessibility/ohos_accessibility_bridge.cpp", "./accessibility/ohos_accessibility_features.cpp", "./accessibility/ohos_accessibility_manager.cpp", + "./accessibility/native_accessibility_channel.cpp" ] # Set flag to stop headers being directly included (library users should not do this) diff --git a/shell/platform/ohos/accessibility/native_accessibility_channel.cpp b/shell/platform/ohos/accessibility/native_accessibility_channel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1d3e8a7a3aa374ae7bbfcaea280754673fbfa46d --- /dev/null +++ b/shell/platform/ohos/accessibility/native_accessibility_channel.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "native_accessibility_channel.h" +#include "flutter/shell/platform/ohos/ohos_shell_holder.h" +#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h" + +namespace flutter { + + NativeAccessibilityChannel::NativeAccessibilityChannel() {} + + NativeAccessibilityChannel::~NativeAccessibilityChannel() {} + + /** + * 通知flutter框架ohos平台无障碍屏幕朗读已开启 + */ + void NativeAccessibilityChannel::OnOhosAccessibilityEnabled(int64_t shellHolderId) + { + FML_DLOG(INFO) << "NativeAccessibilityChannel -> OnOhosAccessibilityEnabled"; + this->SetSemanticsEnabled(shellHolderId, true); + } + + /** + * 通知flutter框架ohos平台无障碍屏幕朗读未开启 + */ + void NativeAccessibilityChannel::OnOhosAccessibilityDisabled(int64_t shellHolderId) + { + FML_DLOG(INFO) << "NativeAccessibilityChannel -> OnOhosAccessibilityDisabled"; + this->SetSemanticsEnabled(shellHolderId, false); + } + + /** + * Native无障碍通道传递语义感知,若开启则实时更新语义树信息 + */ + void NativeAccessibilityChannel::SetSemanticsEnabled(int64_t shellHolderId, + bool enabled) + { + auto ohos_shell_holder = + reinterpret_cast(shellHolderId); + ohos_shell_holder->GetPlatformView()->SetSemanticsEnabled(enabled); + } + + /** + * Native无障碍通道设置无障碍特征类型,如:无障碍导航、字体加粗等 + */ + void NativeAccessibilityChannel::SetAccessibilityFeatures(int64_t shellHolderId, + int32_t flags) + { + auto ohos_shell_holder = + reinterpret_cast(shellHolderId); + ohos_shell_holder->GetPlatformView()->SetAccessibilityFeatures(flags); + } + + /** + * Native无障碍通道分发flutter屏幕语义动作,如:点击、滑动等 + */ + void NativeAccessibilityChannel::DispatchSemanticsAction( + int64_t shellHolderId, + int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args) + { + auto ohos_shell_holder = + reinterpret_cast(shellHolderId); + ohos_shell_holder->GetPlatformView()->PlatformView::DispatchSemanticsAction(id, action, fml::MallocMapping()); + } + + /** + * 更新flutter无障碍相关语义信息 + */ + void NativeAccessibilityChannel::UpdateSemantics( + flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions) + { + auto ohos_a11y_bridge = OhosAccessibilityBridge::GetInstance(); + ohos_a11y_bridge->updateSemantics(update, actions); + } + + /** + * 设置无障碍消息处理器,通过无障碍通道发送处理dart侧传递的相关信息 + */ + void NativeAccessibilityChannel::SetAccessibilityMessageHandler( + std::shared_ptr handler) + { + this->handler = handler; + } + + /** + * 利用通道内部类AccessibilityMessageHandler处理主动播报事件 + */ + void NativeAccessibilityChannel::AccessibilityMessageHandler::Announce( + std::unique_ptr& message) + { + auto ohos_a11y_bridge = OhosAccessibilityBridge::GetInstance(); + ohos_a11y_bridge->Announce(message); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/accessibility/native_accessibility_channel.h b/shell/platform/ohos/accessibility/native_accessibility_channel.h new file mode 100644 index 0000000000000000000000000000000000000000..29d6e895c459d9ab18537c3e57330d0774021bd2 --- /dev/null +++ b/shell/platform/ohos/accessibility/native_accessibility_channel.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OHOS_NATIVE_ACCESSIBILITY_CHANNEL_H +#define OHOS_NATIVE_ACCESSIBILITY_CHANNEL_H +#include +#include "flutter/fml/mapping.h" +#include "flutter/lib/ui/semantics/semantics_node.h" +#include "flutter/lib/ui/semantics/custom_accessibility_action.h" +namespace flutter { + +class NativeAccessibilityChannel { + public: + NativeAccessibilityChannel(); + ~NativeAccessibilityChannel(); + + void OnOhosAccessibilityEnabled(int64_t shellHolderId); + + void OnOhosAccessibilityDisabled(int64_t shellHolderId); + + void SetSemanticsEnabled(int64_t shellHolderId, bool enabled); + + void SetAccessibilityFeatures(int64_t shellHolderId, int32_t flags); + + void DispatchSemanticsAction(int64_t shellHolderId, + int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args); + + void UpdateSemantics(flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions); + + class AccessibilityMessageHandler { + public: + void Announce(std::unique_ptr& message); + + void OnTap(int32_t nodeId); + + void OnLongPress(int32_t nodeId); + + void OnTooltip(std::unique_ptr& message); + }; + + void SetAccessibilityMessageHandler( + std::shared_ptr handler); + + private: + std::shared_ptr handler; +}; +} + +#endif \ No newline at end of file diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp index 7a8b081d9cee93d8ccbe23ab28690e1d19f0ed6a..3875792883f68098255358397a1c4130d1734083 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp +++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,95 +15,66 @@ #include "ohos_accessibility_bridge.h" #include #include +#include #include "flutter/fml/logging.h" +#include "flutter/shell/platform/ohos/ohos_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" +#define ARKUI_SUCCEED_CODE 0 +#define ARKUI_FAILED_CODE -1 +#define ARKUI_BAD_PARAM_CODE -2 +#define ARKUI_OOM_CODE -3 +#define ARKUI_ACCESSIBILITY_CALL_CHECK(X) \ + do { \ + int32_t ret = X; \ + if (ret != ARKUI_SUCCEED_CODE) { \ + LOGE("Failed arkui a11y function call, error code:%{public}d", ret); \ + } \ + } while (false) \ + namespace flutter { - -OhosAccessibilityBridge OhosAccessibilityBridge::bridgeInstance; - -OhosAccessibilityBridge::OhosAccessibilityBridge() {}; -OhosAccessibilityBridge::~OhosAccessibilityBridge() {}; +OhosAccessibilityBridge* OhosAccessibilityBridge::bridgeInstance = nullptr; 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; + if(!bridgeInstance) { + bridgeInstance = new OhosAccessibilityBridge(); } - OH_ArkUI_SendAccessibilityAsyncEvent(provider_, pageUpdateEventInfo, - callback); - - OH_ArkUI_DestoryAccessibilityEventInfo(pageUpdateEventInfo); - OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo); + return bridgeInstance; +} - FML_DLOG(INFO) << "PageStateUpdate is end"; +void OhosAccessibilityBridge::DestroyInstance() { + delete bridgeInstance; + bridgeInstance = nullptr; } +OhosAccessibilityBridge::OhosAccessibilityBridge() {} /** - * 特定节点的焦点请求 (当页面更新时自动请求id=0节点获焦) + * 监听当前ohos平台是否开启无障碍屏幕朗读服务 */ -void OhosAccessibilityBridge::RequestFocusWhenPageUpdate() +void OhosAccessibilityBridge::OnOhosAccessibilityStateChange( + int64_t shellHolderId, + bool ohosAccessibilityEnabled) { - 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); + native_shell_holder_id_ = shellHolderId; + nativeAccessibilityChannel_ = std::make_shared(); + accessibilityFeatures_ = std::make_shared(); - 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; + if (ohosAccessibilityEnabled) { + nativeAccessibilityChannel_->OnOhosAccessibilityEnabled(native_shell_holder_id_); + } else { + accessibilityFeatures_->SetAccessibleNavigation(false, native_shell_holder_id_); + nativeAccessibilityChannel_->OnOhosAccessibilityDisabled(native_shell_holder_id_); } - OH_ArkUI_SendAccessibilityAsyncEvent(provider_, reqFocusEventInfo, callback); +} - OH_ArkUI_DestoryAccessibilityEventInfo(reqFocusEventInfo); - OH_ArkUI_DestoryAccessibilityElementInfo(elementInfo); +void OhosAccessibilityBridge::SetNativeShellHolderId(int64_t id) +{ + this->native_shell_holder_id_ = id; } /** @@ -113,7 +84,7 @@ void OhosAccessibilityBridge::updateSemantics( flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { - FML_DLOG(INFO) << ("OhosAccessibilityBridge::updateSemantics is called"); + FML_DLOG(INFO) << ("OhosAccessibilityBridge::UpdateSemantics()"); // 当flutter页面更新时,自动请求id=0节点组件获焦(滑动组件除外) if (IS_FLUTTER_NAVIGATE) { @@ -129,7 +100,7 @@ void OhosAccessibilityBridge::updateSemantics( // set struct SemanticsNodeExtent auto nodeEx = SetAndGetSemanticsNodeExtent(node); - // print semantics node and flags info for debugging + //print semantics node and flags info for debugging GetSemanticsNodeDebugInfo(node); GetSemanticsFlagsDebugInfo(node); @@ -140,16 +111,6 @@ void OhosAccessibilityBridge::updateSemantics( */ 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++) { @@ -160,7 +121,7 @@ void OhosAccessibilityBridge::updateSemantics( } // 当滑动节点产生滑动,并执行滑动处理 - if (HasScrolled(node)) { + if (HasScrolled(node) && IsNodeVisible(node)) { ArkUI_AccessibilityElementInfo* _elementInfo = OH_ArkUI_CreateAccessibilityElementInfo(); @@ -168,13 +129,15 @@ void OhosAccessibilityBridge::updateSemantics( FlutterScrollExecution(node, _elementInfo); OH_ArkUI_DestoryAccessibilityElementInfo(_elementInfo); + _elementInfo = nullptr; } - // 判断是否触发liveRegion活动区,当前节点是否活跃(暂不影响正常功能) + // 判断是否触发liveRegion活动区,当前节点是否活跃 if (node.HasFlag(FLAGS_::kIsLiveRegion)) { FML_DLOG(INFO) - << "UpdateSemantics -> flutterNode is kIsLiveRegion, node.id=" - << node.id; + << "UpdateSemantics -> LiveRegion, node.id=" << node.id; + FlutterPageUpdate(ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_PAGE_CONTENT_UPDATE); } } @@ -195,6 +158,11 @@ void OhosAccessibilityBridge::updateSemantics( << item.second << ")"; } + //打印按层次遍历排序的flutter语义树节点id数组 + std::vector levelOrderTraversalTree = GetLevelOrderTraversalTree(0); + for (const auto& item: levelOrderTraversalTree) { + FML_DLOG(INFO) << "LevelOrderTraversalTree: { " << item << " }"; + } FML_DLOG(INFO) << "=== UpdateSemantics is end ==="; } @@ -238,172 +206,185 @@ void OhosAccessibilityBridge::FlutterScrollExecution( if (node.scrollChildren > 0) { // 配置当前滑动组件的子节点总数 int32_t itemCount = node.scrollChildren; - OH_ArkUI_AccessibilityElementInfoSetItemCount(elementInfoFromList, - itemCount); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetItemCount( + elementInfoFromList, itemCount) + ); // 设置当前页面可见的起始滑动index int32_t startItemIndex = node.scrollIndex; - OH_ArkUI_AccessibilityElementInfoSetStartItemIndex(elementInfoFromList, - startItemIndex); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); + auto childNode = GetFlutterSemanticsNode(childId); if (!childNode.HasFlag(FLAGS_::kIsHidden)) { visibleChildren += 1; } } // 当可见滑动子节点数量超过滑动组件总子节点数量 if (node.scrollIndex + visibleChildren > node.scrollChildren) { - FML_DLOG(ERROR) + FML_DLOG(WARNING) << "FlutterScrollExecution -> Scroll index is out of bounds"; } // 当滑动击中子节点数量为0 if (!node.childrenInHitTestOrder.size()) { - FML_DLOG(ERROR) << "FlutterScrollExecution -> Had scrollChildren but no " - "childrenInHitTestOrder"; + FML_DLOG(WARNING) << "FlutterScrollExecution -> Had scrollChildren but no " + "childrenInHitTestOrder"; } // 设置当前页面可见的末尾滑动index int32_t endItemIndex = node.scrollIndex + visibleChildren - 1; - OH_ArkUI_AccessibilityElementInfoSetEndItemIndex(elementInfoFromList, - endItemIndex); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetEndItemIndex(elementInfoFromList, + endItemIndex) + ); + } } /** - * extent common struct SemanticsNode to - * derived struct SemanticsNodeExtent + * 当页面内容/状态更新事件,在页面转换、切换、调整大小时发送页面状态更新事件 */ -SemanticsNodeExtent OhosAccessibilityBridge::SetAndGetSemanticsNodeExtent( - flutter::SemanticsNode node) +void OhosAccessibilityBridge::FlutterPageUpdate( + ArkUI_AccessibilityEventType eventType) { - 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); + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "PageStateUpdate ->" + "AccessibilityProvider = nullptr"; + return; + } + ArkUI_AccessibilityEventInfo* pageUpdateEventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); - 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetEventType(pageUpdateEventInfo, eventType) + ); - return nodeEx; -} + auto callback = [](int32_t errorCode) { + FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode; + }; -/** - * 判断当前节点是否已经滑动 - */ -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); + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, pageUpdateEventInfo, + callback); + OH_ArkUI_DestoryAccessibilityEventInfo(pageUpdateEventInfo); + pageUpdateEventInfo = nullptr; } + /** - * 判断当前节点组件是否是滑动组件,如: listview, gridview等 + * 特定节点的焦点请求 (当页面更新时自动请求id=0节点获焦) */ -bool OhosAccessibilityBridge::IsScrollableWidget( - flutter::SemanticsNode flutterNode) +void OhosAccessibilityBridge::RequestFocusWhenPageUpdate() { - return flutterNode.HasFlag(FLAGS_::kHasImplicitScrolling); + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "RequestFocusWhenPageUpdate ->" + "AccessibilityProvider = nullptr"; + return; + } + ArkUI_AccessibilityEventInfo* reqFocusEventInfo = + OH_ArkUI_CreateAccessibilityEventInfo(); + ArkUI_AccessibilityElementInfo* elementInfo = + OH_ArkUI_CreateAccessibilityElementInfo(); + + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetEventType( + reqFocusEventInfo, + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_REQUEST_ACCESSIBILITY_FOCUS) + ); + + int32_t requestFocusId = 0; + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetRequestFocusId(reqFocusEventInfo, + requestFocusId) + ); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetElementInfo(reqFocusEventInfo, elementInfo) + ); + + auto callback = [](int32_t errorCode) { + FML_DLOG(WARNING) << "PageStateUpdate callback-> errorCode =" << errorCode; + }; + OH_ArkUI_SendAccessibilityAsyncEvent(provider_, reqFocusEventInfo, callback); + + OH_ArkUI_DestoryAccessibilityEventInfo(reqFocusEventInfo); + reqFocusEventInfo = nullptr; + OH_ArkUI_DestoryAccessibilityElementInfo(elementInfo); + elementInfo = nullptr; } /** * 主动播报特定文本 */ -void OhosAccessibilityBridge::announce(std::unique_ptr& message) +void OhosAccessibilityBridge::Announce(std::unique_ptr& message) { + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "announce ->" + "AccessibilityProvider = nullptr"; + return; + } // 创建并设置屏幕朗读事件 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: ") + + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetEventType( + announceEventInfo, + ArkUI_AccessibilityEventType:: + ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_ANNOUNCE_FOR_ACCESSIBILITY) + ); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityEventSetTextAnnouncedForAccessibility( + announceEventInfo, message.get()) + ); + FML_DLOG(INFO) << ("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); + announceEventInfo = nullptr; return; } +//获取根节点 +flutter::SemanticsNode OhosAccessibilityBridge::GetFlutterRootSemanticsNode() +{ + if (!flutterSemanticsTree_.size()) { + FML_DLOG(ERROR) + << "GetFlutterRootSemanticsNode -> flutterSemanticsTree_.size()=0"; + return {}; + } + if (flutterSemanticsTree_.find(0) == flutterSemanticsTree_.end()) { + FML_DLOG(ERROR) << "GetFlutterRootSemanticsNode -> flutterSemanticsTree_ " + "has no keys = 0"; + return {}; + } + return flutterSemanticsTree_.at(0); +} + /** * 根据nodeid获取或创建flutter语义节点 */ -flutter::SemanticsNode OhosAccessibilityBridge::getOrCreateFlutterSemanticsNode( +flutter::SemanticsNode OhosAccessibilityBridge::GetFlutterSemanticsNode( int32_t id) { flutter::SemanticsNode node; if (flutterSemanticsTree_.count(id) > 0) { return flutterSemanticsTree_.at(id); - FML_DLOG(INFO) << "getOrCreateFlutterSemanticsNode get node.id=" << id; + FML_DLOG(INFO) << "GetFlutterSemanticsNode get node.id=" << id; } else { FML_DLOG(ERROR) - << "getOrCreateFlutterSemanticsNode flutterSemanticsTree_ = null" << id; - return flutter::SemanticsNode{}; + << "GetFlutterSemanticsNode flutterSemanticsTree_ = null" << id; + return {}; } } @@ -436,59 +417,74 @@ void OhosAccessibilityBridge::FlutterTreeToArkuiTree( 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfo, &rect) + ); // 设置elementinfo的action类型 std::string widget_type = GetNodeComponentType(flutterNode); FlutterSetElementInfoOperationActions(elementInfo, widget_type); // 设置elementid - OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfo, flutterNode.id); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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; + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, ARKUI_ACCESSIBILITY_ROOT_PARENT_ID) + ); } else { - OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, parentId); - FML_DLOG(INFO) << "FlutterTreeToArkuiTree parent.id= " << parentId; + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfo, parentId) + ); } // 设置孩子节点 - int32_t childCount = - static_cast(flutterNode.childrenInTraversalOrder.size()); + int32_t childCount = flutterNode.childrenInTraversalOrder.size(); + auto childrenIdsVec = flutterNode.childrenInTraversalOrder; + std::sort(childrenIdsVec.begin(), childrenIdsVec.end()); int64_t childNodeIds[childCount]; for (int32_t i = 0; i < childCount; i++) { - childNodeIds[i] = - static_cast(flutterNode.childrenInTraversalOrder[i]); + childNodeIds[i] = static_cast(childrenIdsVec[i]); FML_DLOG(INFO) << "FlutterTreeToArkuiTree flutterNode.id= " << flutterNode.id << " childCount= " << childCount << " childNodeId=" << childNodeIds[i]; } - OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfo, childCount, - childNodeIds); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); - + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetCheckable(elementInfo, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfo, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfo, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfo, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfo, true)); + // 设置组件类型 std::string componentTypeName = GetNodeComponentType(flutterNode); - OH_ArkUI_AccessibilityElementInfoSetComponentType( - elementInfo, componentTypeName.c_str()); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetComponentType( + elementInfo, componentTypeName.c_str()) + ); std::string contents = componentTypeName + "_content"; - OH_ArkUI_AccessibilityElementInfoSetContents(elementInfo, contents.c_str()); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText( + elementInfo, flutterNode.label.c_str()) + ); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfo, "yes")); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfo, false)); } FML_DLOG(INFO) << "FlutterTreeToArkuiTree is end"; } @@ -498,18 +494,21 @@ void OhosAccessibilityBridge::FlutterTreeToArkuiTree( */ 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; + return ARKUI_ACCESSIBILITY_ROOT_PARENT_ID; + } + if (elementId == -1) { + return ARKUI_ACCESSIBILITY_ROOT_PARENT_ID; } + int32_t childElementId = static_cast(elementId); for (const auto& item : parentChildIdVec) { if (item.second == childElementId) { return item.first; } } - return -2100000; + return RET_ERROR_STATE_CODE; } /** @@ -566,7 +565,7 @@ void OhosAccessibilityBridge::ConvertChildRelativeRectToScreenRect( // 获取当前flutter节点的父节点的相对rect int32_t parentId = GetParentId(currNode.id); - auto parentNode = getOrCreateFlutterSemanticsNode(parentId); + auto parentNode = GetFlutterSemanticsNode(parentId); auto parentRight = parentNode.rect.fRight; auto parentBottom = parentNode.rect.fBottom; @@ -577,14 +576,17 @@ void OhosAccessibilityBridge::ConvertChildRelativeRectToScreenRect( auto realParentRight = _rectPairs.second.first; auto realParentBottom = _rectPairs.second.second; - // 获取root节点的绝对坐标 + // 获取root节点的绝对坐标, 即xcomponent屏幕长宽 auto _rootRect = GetAbsoluteScreenRect(0); - auto rootRight = _rootRect.second.first; - auto rootBottom = _rootRect.second.second; + float rootWidth = _rootRect.second.first; + auto rootHeight = _rootRect.second.second; // 真实缩放系数 float realScaleFactor = realParentRight / parentRight * 1.0; - float newLeft, newTop, newRight, newBottom; + float newLeft; + float newTop; + float newRight; + float newBottom; if (_kMScaleX > 1 && _kMScaleY > 1) { // 子节点相对父节点进行变化(缩放、 平移) @@ -598,39 +600,36 @@ void OhosAccessibilityBridge::ConvertChildRelativeRectToScreenRect( // 若当前节点的相对坐标与父亲节点的相对坐标值相同,则直接继承坐标值 if (currRight == parentRight && currBottom == parentBottom) { newLeft = realParentLeft; - newTop = realParentTop; - newRight = realParentRight; + 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; + 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) { + const bool IS_OVER_SCREEN_AREA = newLeft < realParentLeft || + newTop < realParentTop || + newRight > realParentRight || + newBottom > realParentBottom || + newLeft >= newRight || + newTop >= newBottom; + if (IS_OVER_SCREEN_AREA) { 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, + // 防止滑动场景下绿框坐标超出屏幕范围,进行正则化处理 + newLeft = static_cast(newLeft) % static_cast(rootWidth); + newTop = static_cast(newTop) % static_cast(rootHeight); + newRight = static_cast(newRight) % static_cast(rootWidth); + newBottom = static_cast(newBottom) % static_cast(rootHeight); + SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom); } else { SetAbsoluteScreenRect(currNode.id, newLeft, newTop, newRight, newBottom); @@ -653,12 +652,12 @@ void OhosAccessibilityBridge::FlutterNodeToElementInfoById( "elementInfoFromList is null"; return; } - - /** NOTE: when elementId == 0 || elementId == -1 */ + FML_DLOG(INFO) << "FlutterNodeToElementInfoById elementId = " << elementId; + // 当elementId = -1或0时,创建root节点 if (elementId == 0 || elementId == -1) { // 获取flutter的root节点 flutter::SemanticsNode flutterNode = - getOrCreateFlutterSemanticsNode(static_cast(0)); + GetFlutterSemanticsNode(static_cast(0)); // 设置elementinfo的屏幕坐标范围 int32_t left = static_cast(flutterNode.rect.fLeft); @@ -666,7 +665,11 @@ void OhosAccessibilityBridge::FlutterNodeToElementInfoById( 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetScreenRect( + elementInfoFromList, &rect) + ); + // 设置root节点的屏幕绝对坐标rect SetAbsoluteScreenRect(0, left, top, right, bottom); @@ -675,49 +678,57 @@ void OhosAccessibilityBridge::FlutterNodeToElementInfoById( FlutterSetElementInfoOperationActions(elementInfoFromList, widget_type); // 根据flutternode信息配置对应的elementinfo - OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, 0); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, 0) + ); + // NOTE: arkui无障碍子系统强制设置root的父节点id = -2100000 (严禁更改) - OH_ArkUI_AccessibilityElementInfoSetParentId(elementInfoFromList, -2100000); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetParentId( + elementInfoFromList, ARKUI_ACCESSIBILITY_ROOT_PARENT_ID) + ); + // 设置无障碍播报文本 - 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText( + elementInfoFromList, flutterNode.label.empty() ? flutterNode.hint.c_str() : flutterNode.label.c_str()) + ); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel( + elementInfoFromList, "yes") + ); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup( + elementInfoFromList, false) + ); // 配置child节点信息 - int32_t childCount = - static_cast(flutterNode.childrenInTraversalOrder.size()); + int32_t childCount = flutterNode.childrenInTraversalOrder.size(); + auto childrenIdsVec = flutterNode.childrenInTraversalOrder; + std::sort(childrenIdsVec.begin(), childrenIdsVec.end()); int64_t childNodeIds[childCount]; for (int32_t i = 0; i < childCount; i++) { - childNodeIds[i] = - static_cast(flutterNode.childrenInTraversalOrder[i]); + childNodeIds[i] = static_cast(childrenIdsVec[i]); FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> elementid=0 childCount=" << childCount << " childNodeIds=" << childNodeIds[i]; } - OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, - childCount, childNodeIds); - + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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; + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true)); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, "root")); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityElementInfoSetContents(elementInfoFromList, flutterNode.label.c_str())); + } else { + //当elementId >= 1时,根据flutter节点信息配置elementinfo无障碍属性 + FlutterSetElementInfoProperties(elementInfoFromList, elementId); } - - /** NOTE: when elementId >= 1 */ - FML_DLOG(INFO) << "FlutterNodeToElementInfoById elementId = " << elementId; - - // 根据flutter节点信息配置elementinfo无障碍属性 - FlutterSetElementInfoProperties(elementInfoFromList, elementId); - FML_DLOG(INFO) << "=== OhosAccessibilityBridge::FlutterNodeToElementInfoById is end ==="; } @@ -783,9 +794,10 @@ void OhosAccessibilityBridge::FlutterSetElementInfoOperationActions( ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION; actions[9].description = "光标位置设置"; - OH_ArkUI_AccessibilityElementInfoSetOperationActions( - elementInfoFromList, actionTypeNum, actions); - + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions) + ); } else if (widget_type == "scrollable") { // if node is a scrollable component int32_t actionTypeNum = 5; @@ -811,9 +823,10 @@ void OhosAccessibilityBridge::FlutterSetElementInfoOperationActions( ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD; actions[4].description = "向下滑动"; - OH_ArkUI_AccessibilityElementInfoSetOperationActions( - elementInfoFromList, actionTypeNum, actions); - + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions) + ); } else { // set common component action types int32_t actionTypeNum = 3; @@ -831,8 +844,10 @@ void OhosAccessibilityBridge::FlutterSetElementInfoOperationActions( ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CLICK; actions[2].description = "点击动作"; - OH_ArkUI_AccessibilityElementInfoSetOperationActions( - elementInfoFromList, actionTypeNum, actions); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetOperationActions( + elementInfoFromList, actionTypeNum, actions) + ); } } @@ -844,11 +859,14 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( int64_t elementId) { flutter::SemanticsNode flutterNode = - getOrCreateFlutterSemanticsNode(static_cast(elementId)); + GetFlutterSemanticsNode(static_cast(elementId)); // set elementinfo id - OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, - flutterNode.id); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetElementId(elementInfoFromList, + flutterNode.id) + ); + // convert relative rect to absolute rect ConvertChildRelativeRectToScreenRect(flutterNode); auto rectPairs = GetAbsoluteScreenRect(flutterNode.id); @@ -858,11 +876,13 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( int32_t right = rectPairs.second.first; int32_t bottom = rectPairs.second.second; ArkUI_AccessibleRect rect = {left, top, right, bottom}; - OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetScreenRect(elementInfoFromList, &rect) + ); FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> node.id= " - << flutterNode.id << " SceenRect = (" << left << ", " << top - << ", " << right << ", " << bottom << ")"; - + << flutterNode.id << " SceenRect = (" << left << ", " << top + << ", " << right << ", " << bottom << ")"; + // 配置arkui的elementinfo可操作动作属性 if (IsTextField(flutterNode)) { // 若当前flutter节点为文本输入框组件 @@ -880,77 +900,94 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( // 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; - } + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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()); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityText(elementInfoFromList, + text.c_str()) + ); FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetAccessibilityText = " << text; - + //set contents (same as AccessibilityText) + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetContents(elementInfoFromList, text.c_str()) + ); std::string hint = flutterNode.hint; - OH_ArkUI_AccessibilityElementInfoSetHintText(elementInfoFromList, - hint.c_str()); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetHintText(elementInfoFromList, + hint.c_str()) + ); // set chidren elementinfo ids - int32_t childCount = - static_cast(flutterNode.childrenInTraversalOrder.size()); + int32_t childCount = flutterNode.childrenInTraversalOrder.size(); + auto childrenIdsVec = flutterNode.childrenInTraversalOrder; + std::sort(childrenIdsVec.begin(), childrenIdsVec.end()); int64_t childNodeIds[childCount]; for (int32_t i = 0; i < childCount; i++) { - childNodeIds[i] = - static_cast(flutterNode.childrenInTraversalOrder[i]); + childNodeIds[i] = static_cast(childrenIdsVec[i]); FML_DLOG(INFO) << "FlutterNodeToElementInfoById -> elementid=" << elementId << " childCount=" << childCount << " childNodeIds=" << childNodeIds[i]; } - OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, - childCount, childNodeIds); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetChildNodeIds(elementInfoFromList, + childCount, childNodeIds) + ); /** * 根据当前flutter节点的SemanticsFlags特性,配置对应的elmentinfo属性 */ // 判断当前节点是否可点击 if (IsNodeClickable(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetClickable(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetClickable -> true"; } // 判断当前节点是否可获焦点 if (IsNodeFocusable(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetFocusable(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetFocusable -> true"; } // 判断当前节点是否为密码输入框 if (IsNodePassword(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetIsPassword(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetChecked(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetChecked -> true"; } // 判断当前节点组件是否可显示 if (IsNodeVisible(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetVisible(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetVisible -> true"; } @@ -962,13 +999,17 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( } // 判断当前节点组件是否可滑动 if (IsNodeScrollable(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetScrollable(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetScrollable(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetScrollable -> true"; } // 判断当前节点组件是否可编辑(文本输入框) if (IsTextField(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetEditable(elementInfoFromList, true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetEditable(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetEditable -> true"; } @@ -979,15 +1020,18 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( } // 判断当前节点组件是否支持长按 if (IsNodeHasLongPress(flutterNode)) { - OH_ArkUI_AccessibilityElementInfoSetLongClickable(elementInfoFromList, - true); + ARKUI_ACCESSIBILITY_CALL_CHECK( + 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetEnabled(elementInfoFromList, true) + ); FML_DLOG(INFO) << "flutterNode.id=" << flutterNode.id << " OH_ArkUI_AccessibilityElementInfoSetEnabled -> true"; } @@ -998,11 +1042,14 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( << componentTypeName; // flutter节点对应elementinfo所属的组件类型(如:root, button,text等) if (elementId == 0) { - OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, - "root"); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, "root") + ); } else { - OH_ArkUI_AccessibilityElementInfoSetComponentType( - elementInfoFromList, componentTypeName.c_str()); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetComponentType(elementInfoFromList, + componentTypeName.c_str()) + ); } FML_DLOG(INFO) << "FlutterNodeToElementInfoById SetComponentType: " << componentTypeName; @@ -1014,116 +1061,82 @@ void OhosAccessibilityBridge::FlutterSetElementInfoProperties( * “no”:当前组件不可被无障碍辅助服务所识别 * “no-hide-descendants”:当前组件及其所有子组件不可被无障碍辅助服务所识别 */ - OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, - "yes"); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityLevel(elementInfoFromList, "yes"); + ); // 无障碍组,设置为true时表示该组件及其所有子组件为一整个可以选中的组件,无障碍服务将不再关注其子组件内容。默认值:false - OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, - false); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityGroup(elementInfoFromList, false); + ); } /** - * 判断当前节点是否为textfield文本框 + * 将flutter无障碍语义树的转化为层次遍历顺序存储, + * 并按该顺序构建arkui语义树,以实现DevEco Testing + * UIViewer、Hypium自动化测试工具对flutter组件树的可视化 */ -bool OhosAccessibilityBridge::IsTextField(flutter::SemanticsNode flutterNode) +std::vector OhosAccessibilityBridge::GetLevelOrderTraversalTree(int32_t rootId) { - return flutterNode.HasFlag(FLAGS_::kIsTextField); + std::vector levelOrderTraversalTree; + std::queue semanticsQue; + + auto root = GetFlutterSemanticsNode(rootId); + semanticsQue.push(root); + + while (!semanticsQue.empty()) { + uint32_t queSize = semanticsQue.size(); + for (uint32_t i=0; i(currNode.id)); + + std::sort(currNode.childrenInTraversalOrder.begin(), + currNode.childrenInTraversalOrder.end()); + for (const auto& childId: currNode.childrenInTraversalOrder) { + auto childNode = GetFlutterSemanticsNode(childId); + semanticsQue.push(childNode); + } + } + } + return levelOrderTraversalTree; } + /** - * 判断当前节点是否为滑动条slider类型 + * 创建并配置完整arkui无障碍语义树 */ -bool OhosAccessibilityBridge::IsSlider(flutter::SemanticsNode flutterNode) +void OhosAccessibilityBridge::BuildArkUISemanticsTree( + int64_t elementId, + ArkUI_AccessibilityElementInfo* elementInfoFromList, + ArkUI_AccessibilityElementInfoList* elementList) { - return flutterNode.HasFlag(FLAGS_::kIsSlider); + //配置root节点信息 + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + //获取flutter无障碍语义树的节点总数 + auto levelOrderTreeVec = GetLevelOrderTraversalTree(0); + int64_t elementInfoCount = levelOrderTreeVec.size(); + //创建并配置节点id >= 1的全部节点 + for (int64_t i = 1; i < elementInfoCount; i++) { + int64_t levelOrderId = levelOrderTreeVec[i]; + auto newNode = GetFlutterSemanticsNode(levelOrderId); + //当节点为隐藏状态时,自动规避 + if (IsNodeVisible(newNode)) { + ArkUI_AccessibilityElementInfo* newElementInfo = + OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList); + //配置当前子节点信息 + FlutterNodeToElementInfoById(newElementInfo, levelOrderId); + } + } } + /** - * 判断当前flutter节点组件是否可点击 + * Called to obtain element information based on a specified node. + * NOTE:该arkui接口需要在系统无障碍服务开启时,才能触发调用 */ -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) +int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosById( + int64_t elementId, + ArkUI_AccessibilitySearchMode mode, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) { FML_DLOG(INFO) << "#### FindAccessibilityNodeInfosById input-params ####: elementId = " @@ -1140,6 +1153,11 @@ int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosById( return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; } + // 开启无障碍导航功能 + if(elementId == -1 || elementId == 0) { + accessibilityFeatures_->SetAccessibleNavigation(true, native_shell_holder_id_); + } + // 从elementinfolist中获取elementinfo ArkUI_AccessibilityElementInfo* elementInfoFromList = OH_ArkUI_AddAndGetAccessibilityElementInfo(elementList); @@ -1149,176 +1167,64 @@ int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosById( return ARKUI_ACCESSIBILITY_NATIVE_RESULT_FAILED; } + // 过滤非当前屏幕显示的语义节点创建、配置,防止溢出屏幕坐标绘制bug以及优化性能开销 + auto flutterNode = GetFlutterSemanticsNode(static_cast(elementId)); + bool VISIBLE_STATE = elementId == -1; + if (!VISIBLE_STATE && !IsNodeVisible(flutterNode)) { + FML_DLOG(INFO) << "filter hidden nodes, elementId:" << elementId; + 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); - } - + BuildArkUISemanticsTree(elementId, elementInfoFromList, elementList); } else if (mode == ArkUI_AccessibilitySearchMode:: ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_PREDECESSORS) { /** Search for parent nodes. (mode = 1) */ - FlutterNodeToElementInfoById(elementInfoFromList, elementId); - + if (IsNodeVisible(flutterNode)) { + FlutterNodeToElementInfoById(elementInfoFromList, elementId); + } } else if (mode == ArkUI_AccessibilitySearchMode:: ARKUI_ACCESSIBILITY_NATIVE_SEARCH_MODE_PREFETCH_SIBLINGS) { /** Search for sibling nodes. (mode = 2) */ - FlutterNodeToElementInfoById(elementInfoFromList, elementId); - + if (IsNodeVisible(flutterNode)) { + 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); - + if (IsNodeVisible(flutterNode)) { + 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); - } - + BuildArkUISemanticsTree(elementId, elementInfoFromList, elementList); } 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 + * 解析flutter语义动作,并通过NativAccessibilityChannel分发 */ 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); + nativeAccessibilityChannel_->DispatchSemanticsAction(native_shell_holder_id_, + id, + action, + fml::MallocMapping()); } /** @@ -1343,7 +1249,7 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( // 获取当前elementid对应的flutter语义节点 auto flutterNode = - getOrCreateFlutterSemanticsNode(static_cast(elementId)); + GetFlutterSemanticsNode(static_cast(elementId)); // 根据当前elementid和无障碍动作类型,发送无障碍事件 switch (action) { @@ -1356,14 +1262,12 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( 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: { @@ -1374,14 +1278,12 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( 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: { @@ -1389,25 +1291,20 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( auto flutterGainFocusAction = ArkuiActionsToFlutterActions(action); DispatchSemanticsAction(static_cast(elementId), flutterGainFocusAction, {}); - - /** Accessibility focus event, sent after the UI component responds. 32768 - */ + // 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: { @@ -1415,7 +1312,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( auto flutterLoseFocusAction = ArkuiActionsToFlutterActions(action); DispatchSemanticsAction(static_cast(elementId), flutterLoseFocusAction, {}); - /** Accessibility focus cleared event, sent after the UI component * responds. 65536 */ auto clearFocusEventType = ArkUI_AccessibilityEventType:: @@ -1426,7 +1322,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( << clearFocusEventType << ")"; break; } - /** Forward scroll action. 256 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_FORWARD: { @@ -1435,21 +1330,18 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( 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); @@ -1466,7 +1358,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( } break; } - /** Backward scroll action. 512 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SCROLL_BACKWARD: { @@ -1475,11 +1366,9 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( 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; @@ -1489,7 +1378,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( ARKUI_ACCESSIBILITY_NATIVE_EVENT_TYPE_SELECTED); DispatchSemanticsAction(static_cast(elementId), ACTIONS_::kDecrease, {}); - } else { } std::string currComponetType = GetNodeComponentType(flutterNode); @@ -1507,7 +1395,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( } break; } - /** Copy action for text content. 1024 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_COPY: { @@ -1517,7 +1404,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( {}); break; } - /** Paste action for text content. 2048 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_PASTE: { @@ -1527,7 +1413,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( {}); break; } - /** Cut action for text content. 4096 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_CUT: @@ -1536,7 +1421,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( 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 */ @@ -1548,7 +1432,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( PerformSelectText(flutterNode, action, actionArguments); break; } - /** Text content setting action. 16384 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_TEXT: { @@ -1558,7 +1441,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( PerformSetText(flutterNode, action, actionArguments); break; } - /** Cursor position setting action. 1048576 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_SET_CURSOR_POSITION: { @@ -1568,7 +1450,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( // 当前os接口不支持该功能,不影响正常屏幕朗读 break; } - /** Invalid action. 0 */ case ArkUI_Accessibility_ActionType:: ARKUI_ACCESSIBILITY_NATIVE_ACTION_TYPE_INVALID: { @@ -1582,7 +1463,6 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( << invalidEventType << ")"; break; } - default: { /** custom semantics action */ } @@ -1593,38 +1473,147 @@ int32_t OhosAccessibilityBridge::ExecuteAccessibilityAction( } /** - * 自定义无障碍异步事件发送 + * Called to obtain element information based on a specified node and text + * content. */ -void OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent( +int32_t OhosAccessibilityBridge::FindAccessibilityNodeInfosByText( int64_t elementId, - ArkUI_AccessibilityEventType eventType) + const char* text, + int32_t requestId, + ArkUI_AccessibilityElementInfoList* elementList) { - // 1.创建eventInfo对象 - ArkUI_AccessibilityEventInfo* eventInfo = - OH_ArkUI_CreateAccessibilityEventInfo(); - if (eventInfo == nullptr) { - FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent " - "OH_ArkUI_CreateAccessibilityEventInfo eventInfo = null"; - return; - } + 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 ==="; - // 2.创建的elementinfo并根据对应id的flutternode进行属性初始化 - ArkUI_AccessibilityElementInfo* _elementInfo = - OH_ArkUI_CreateAccessibilityElementInfo(); - FlutterNodeToElementInfoById(_elementInfo, elementId); - // 若为获焦事件,则设置当前elementinfo获焦 - if (eventType == - ArkUI_AccessibilityEventType:: + 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; + } +} + +/** + * 自定义无障碍异步事件发送 + */ +void OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent( + int64_t elementId, + ArkUI_AccessibilityEventType eventType) +{ + if (provider_ == nullptr) { + FML_DLOG(ERROR) << "Flutter_SendAccessibilityAsyncEvent " + "AccessibilityProvider = nullptr"; + return; + } + // 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); + ARKUI_ACCESSIBILITY_CALL_CHECK( + OH_ArkUI_AccessibilityElementInfoSetAccessibilityFocused(_elementInfo, + true) + ); } // 3.设置发送事件,如配置获焦、失焦、点击、滑动事件 - OH_ArkUI_AccessibilityEventSetEventType(eventInfo, eventType); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityEventSetEventType(eventInfo, eventType)); // 4.将eventinfo事件和当前elementinfo进行绑定 - OH_ArkUI_AccessibilityEventSetElementInfo(eventInfo, _elementInfo); + ARKUI_ACCESSIBILITY_CALL_CHECK(OH_ArkUI_AccessibilityEventSetElementInfo(eventInfo, _elementInfo)); // 5.调用接口发送到ohos侧 auto callback = [](int32_t errorCode) { @@ -1634,16 +1623,13 @@ void OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent( }; // 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); + _elementInfo = nullptr; OH_ArkUI_DestoryAccessibilityEventInfo(eventInfo); + eventInfo = nullptr; FML_DLOG(INFO) << "OhosAccessibilityBridge::Flutter_SendAccessibilityAsyncEvent is end"; @@ -1699,34 +1685,25 @@ std::string OhosAccessibilityBridge::GetNodeComponentType( 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 @@ -1735,33 +1712,146 @@ std::string OhosAccessibilityBridge::GetNodeComponentType( return "Checkbox"; } } - if (node.HasFlag(FLAGS_::kHasToggledState)) { - // arkui没有ToggleSwitch,这里透传为Toggle - return "ToggleSwitch"; + return "Switch"; + } + if (node.HasFlag(FLAGS_::kHasImplicitScrolling)) { + if (node.HasAction(ACTIONS_::kScrollLeft) || + node.HasAction(ACTIONS_::kScrollRight)) { + return "HorizontalScrollView"; + } else { + return "ScrollView"; + } + } + if (node.HasAction(ACTIONS_::kIncrease) || + node.HasAction(ACTIONS_::kDecrease)) { + return "SeekBar"; } - if ((!node.label.empty() || !node.tooltip.empty() || !node.hint.empty())) { return "Text"; } - return "Widget" + std::to_string(node.id); } -// 获取根节点 -flutter::SemanticsNode OhosAccessibilityBridge::getFlutterRootSemanticsNode() +/** + * 判断当前节点是否为textfield文本框 + */ +bool OhosAccessibilityBridge::IsTextField(flutter::SemanticsNode flutterNode) { - 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); + 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); +} +/** + * 判断当前节点是否已经滑动 + */ +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::AddRouteNodes( @@ -1772,7 +1862,7 @@ void OhosAccessibilityBridge::AddRouteNodes( edges.emplace_back(node); } for (auto& childNodeId : node.childrenInTraversalOrder) { - auto childNode = getOrCreateFlutterSemanticsNode(childNodeId); + auto childNode = GetFlutterSemanticsNode(childNodeId); AddRouteNodes(edges, childNode); } } @@ -1783,7 +1873,7 @@ std::string OhosAccessibilityBridge::GetRouteName(flutter::SemanticsNode node) return node.label; } for (auto& childNodeId : node.childrenInTraversalOrder) { - auto childNode = getOrCreateFlutterSemanticsNode(childNodeId); + auto childNode = GetFlutterSemanticsNode(childNodeId); std::string newName = GetRouteName(childNode); if (!newName.empty()) { return newName; @@ -1840,6 +1930,51 @@ void OhosAccessibilityBridge::ClearFlutterSemanticsCaches() flutterNavigationVec_.clear(); } +/** + * 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; +} + void OhosAccessibilityBridge::GetSemanticsNodeDebugInfo( flutter::SemanticsNode node) { @@ -1913,6 +2048,8 @@ void OhosAccessibilityBridge::GetSemanticsFlagsDebugInfo( flutter::SemanticsNode node) { FML_DLOG(INFO) << "----------------SemanticsFlags-------------------------"; + FML_DLOG(INFO) << "node.id=" << node.id; + FML_DLOG(INFO) << "node.label=" << node.label; FML_DLOG(INFO) << "kHasCheckedState: " << node.HasFlag(FLAGS_::kHasCheckedState); FML_DLOG(INFO) << "kIsChecked:" << node.HasFlag(FLAGS_::kIsChecked); diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h index 602df9370bde6e53633d8b49868305b0aff5ec4a..17d58b92d1de7a138c4c56a39f35d07e8576d17b 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h +++ b/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,30 +12,25 @@ * 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 #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" +#include "native_accessibility_channel.h" +#include "ohos_accessibility_features.h" namespace flutter { typedef flutter::SemanticsFlags FLAGS_; typedef flutter::SemanticsAction ACTIONS_; -typedef flutter::SemanticsNode FLUTTER_NODE_; -/** - * flutter和ohos的无障碍服务桥接 - */ struct AbsoluteRect { float left; float top; @@ -50,7 +45,6 @@ struct AbsoluteRect { struct SemanticsNodeExtent : flutter::SemanticsNode { int32_t parentId = -1; AbsoluteRect abRect = AbsoluteRect::MakeEmpty(); - int32_t previousFlags; int32_t previousActions; int32_t previousTextSelectionBase; @@ -62,18 +56,26 @@ struct SemanticsNodeExtent : flutter::SemanticsNode { std::string previousLabel; }; +/** + * flutter和ohos的无障碍服务桥接 + */ class OhosAccessibilityBridge { public: - OhosAccessibilityBridge(); - ~OhosAccessibilityBridge(); - static OhosAccessibilityBridge* GetInstance(); + static void DestroyInstance(); + OhosAccessibilityBridge(const OhosAccessibilityBridge&) = delete; + OhosAccessibilityBridge& operator=(const OhosAccessibilityBridge&) = delete; - bool isOhosAccessibilityEnabled_; bool IS_FLUTTER_NAVIGATE = false; - int64_t nativeShellHolder_; + int64_t native_shell_holder_id_; ArkUI_AccessibilityProvider* provider_; + void OnOhosAccessibilityStateChange( + int64_t shellHolderId, + bool ohosAccessibilityEnabled); + + void SetNativeShellHolderId(int64_t id); + void updateSemantics(flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions); @@ -81,10 +83,9 @@ class OhosAccessibilityBridge { flutter::SemanticsAction action, fml::MallocMapping args); - void announce(std::unique_ptr& message); + void Announce(std::unique_ptr& message); - // obtain the flutter semnatics node - flutter::SemanticsNode getOrCreateFlutterSemanticsNode(int32_t id); + flutter::SemanticsNode GetFlutterSemanticsNode(int32_t id); int32_t FindAccessibilityNodeInfosById( int64_t elementId, @@ -138,11 +139,18 @@ class OhosAccessibilityBridge { void FlutterScrollExecution( flutter::SemanticsNode node, ArkUI_AccessibilityElementInfo* elementInfoFromList); - + void ClearFlutterSemanticsCaches(); private: - static OhosAccessibilityBridge bridgeInstance; + OhosAccessibilityBridge(); + static OhosAccessibilityBridge* bridgeInstance; + std::shared_ptr nativeAccessibilityChannel_; + std::shared_ptr accessibilityFeatures_; + + // arkui的root节点的父节点id + static const int32_t ARKUI_ACCESSIBILITY_ROOT_PARENT_ID = -2100000; + static const int32_t RET_ERROR_STATE_CODE = -999; 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; @@ -151,9 +159,8 @@ class OhosAccessibilityBridge { flutter::SemanticsNode lastInputFocusedNode; flutter::SemanticsNode accessibilityFocusedNode; - std::shared_ptr ax_manager_; std::vector> parentChildIdVec; - std::unordered_map flutterSemanticsTree_; + std::map flutterSemanticsTree_; std::unordered_map< int32_t, std::pair, std::pair>> @@ -226,8 +233,13 @@ class OhosAccessibilityBridge { std::string widget_type); void FlutterTreeToArkuiTree( ArkUI_AccessibilityElementInfoList* elementInfoList); + void BuildArkUISemanticsTree( + int64_t elementId, + ArkUI_AccessibilityElementInfo* elementInfoFromList, + ArkUI_AccessibilityElementInfoList* elementList); - flutter::SemanticsNode getFlutterRootSemanticsNode(); + std::vector GetLevelOrderTraversalTree(int32_t rootId); + flutter::SemanticsNode GetFlutterRootSemanticsNode(); std::string GetNodeComponentType(const flutter::SemanticsNode& node); flutter::SemanticsAction ArkuiActionsToFlutterActions( ArkUI_Accessibility_ActionType arkui_action); @@ -266,11 +278,65 @@ class OhosAccessibilityBridge { void GetCustomActionDebugInfo( flutter::CustomAccessibilityAction customAccessibilityAction); - void PageStateUpdate(int64_t elementId); + void FlutterPageUpdate(ArkUI_AccessibilityEventType eventType); void RequestFocusWhenPageUpdate(); bool Contains(const std::string source, const std::string target); }; +enum class AccessibilityAction : int32_t { + kTap = 1 << 0, + kLongPress = 1 << 1, + kScrollLeft = 1 << 2, + kScrollRight = 1 << 3, + kScrollUp = 1 << 4, + kScrollDown = 1 << 5, + kIncrease = 1 << 6, + kDecrease = 1 << 7, + kShowOnScreen = 1 << 8, + kMoveCursorForwardByCharacter = 1 << 9, + kMoveCursorBackwardByCharacter = 1 << 10, + kSetSelection = 1 << 11, + kCopy = 1 << 12, + kCut = 1 << 13, + kPaste = 1 << 14, + kDidGainAccessibilityFocus = 1 << 15, + kDidLoseAccessibilityFocus = 1 << 16, + kCustomAction = 1 << 17, + kDismiss = 1 << 18, + kMoveCursorForwardByWord = 1 << 19, + kMoveCursorBackwardByWord = 1 << 20, + kSetText = 1 << 21, +}; + +enum class AccessibilityFlags : int32_t { + kHasCheckedState = 1 << 0, + kIsChecked = 1 << 1, + kIsSelected = 1 << 2, + kIsButton = 1 << 3, + kIsTextField = 1 << 4, + kIsFocused = 1 << 5, + kHasEnabledState = 1 << 6, + kIsEnabled = 1 << 7, + kIsInMutuallyExclusiveGroup = 1 << 8, + kIsHeader = 1 << 9, + kIsObscured = 1 << 10, + kScopesRoute = 1 << 11, + kNamesRoute = 1 << 12, + kIsHidden = 1 << 13, + kIsImage = 1 << 14, + kIsLiveRegion = 1 << 15, + kHasToggledState = 1 << 16, + kIsToggled = 1 << 17, + kHasImplicitScrolling = 1 << 18, + kIsMultiline = 1 << 19, + kIsReadOnly = 1 << 20, + kIsFocusable = 1 << 21, + kIsLink = 1 << 22, + kIsSlider = 1 << 23, + kIsKeyboardKey = 1 << 24, + kIsCheckStateMixed = 1 << 25, +}; + } // 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 index caea517fee6a71dc676e50924a31f6757a2805a1..f82bc0bbb8e4ebc48000f5f9703d6055714b4520 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_features.cpp +++ b/shell/platform/ohos/accessibility/ohos_accessibility_features.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,47 +12,66 @@ * 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 "ohos_accessibility_features.h" #include "flutter/fml/logging.h" +#include "flutter/shell/platform/ohos/ohos_shell_holder.h" namespace flutter { - OhosAccessibilityFeatures OhosAccessibilityFeatures::instance; - - OhosAccessibilityFeatures::OhosAccessibilityFeatures() {}; - OhosAccessibilityFeatures::~OhosAccessibilityFeatures() {}; +OhosAccessibilityFeatures::OhosAccessibilityFeatures() {}; +OhosAccessibilityFeatures::~OhosAccessibilityFeatures() {}; - OhosAccessibilityFeatures* OhosAccessibilityFeatures::GetInstance() { - return &OhosAccessibilityFeatures::instance; +/** + * 无障碍特征之无障碍导航 + */ +void OhosAccessibilityFeatures::SetAccessibleNavigation( + bool isAccessibleNavigation, + int64_t shell_holder_id) +{ + if (ACCESSIBLE_NAVIGATION == isAccessibleNavigation) { + return; } + ACCESSIBLE_NAVIGATION = isAccessibleNavigation; + if (ACCESSIBLE_NAVIGATION) { + accessibilityFeatureFlags |= + static_cast(AccessibilityFeatures::AccessibleNavigation); + FML_DLOG(INFO) << "SetAccessibleNavigation -> accessibilityFeatureFlags: " + << accessibilityFeatureFlags; + } else { + accessibilityFeatureFlags &= + ~static_cast(AccessibilityFeatures::AccessibleNavigation); + } + SendAccessibilityFlags(shell_holder_id); +} - /** - * 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); +/** + * 无障碍特征之字体加粗 + */ +void OhosAccessibilityFeatures::SetBoldText(double fontWeightScale, + int64_t shell_holder_id) { + bool shouldBold = fontWeightScale > 1.0; + if (shouldBold) { + accessibilityFeatureFlags |= + static_cast(AccessibilityFeatures::BoldText); + FML_DLOG(INFO) << "SetBoldText -> accessibilityFeatureFlags: " + << accessibilityFeatureFlags; + } else { + accessibilityFeatureFlags &= + static_cast(AccessibilityFeatures::BoldText); } - - /** - * 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; - } + SendAccessibilityFlags(shell_holder_id); +} + +/** + * send the accessibility flags to flutter dart sdk + */ +void OhosAccessibilityFeatures::SendAccessibilityFlags( + int64_t shell_holder_id) { + nativeAccessibilityChannel_ = std::make_shared(); + nativeAccessibilityChannel_->SetAccessibilityFeatures(shell_holder_id, accessibilityFeatureFlags); + FML_DLOG(INFO) << "SendAccessibilityFlags -> accessibilityFeatureFlags = " + << accessibilityFeatureFlags; + accessibilityFeatureFlags = 0; +} -} \ No newline at end of file +} // namespace flutter \ 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 index 093e89ce11d37c02f40d89151372a3433229f9ff..1163180d65194e5bc38ac4aeb66112dafa7634ba 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_features.h +++ b/shell/platform/ohos/accessibility/ohos_accessibility_features.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,28 +16,43 @@ #define OHOS_ACCESSIBILITY_FEATURES_H #include #include "flutter/lib/ui/window/platform_configuration.h" +#include "native_accessibility_channel.h" #include "flutter/shell/platform/ohos/napi/platform_view_ohos_napi.h" - namespace flutter { class OhosAccessibilityFeatures { - public: - OhosAccessibilityFeatures(); - ~OhosAccessibilityFeatures(); + public: + OhosAccessibilityFeatures(); + ~OhosAccessibilityFeatures(); + + bool ACCESSIBLE_NAVIGATION = false; - static OhosAccessibilityFeatures* GetInstance(); + void SetAccessibleNavigation(bool isAccessibleNavigation, + int64_t shell_holder_id); + void SetBoldText(double fontWeightScale, int64_t shell_holder_id); - void SetBoldText(double fontWeightScale, int64_t shell_holder_id); - void SendAccessibilityFlags(int64_t shell_holder_id); + void SendAccessibilityFlags(int64_t shell_holder_id); - private: - static OhosAccessibilityFeatures instance; + private: + std::shared_ptr nativeAccessibilityChannel_; + int32_t accessibilityFeatureFlags = 0; +}; - // Font weight adjustment (FontWeight.Bold - FontWeight.Normal = w700 - w400 = 300) - static const int32_t BOLD_TEXT_WEIGHT_ADJUSTMENT = 300; - int32_t accessibilityFeatureFlags = 0; +/** + * 无障碍特征枚举类(flutter平台通用) + * 注意:必须同src/flutter/lib/ui/window/platform_configuration.h + * 中的`AccessibilityFeatureFlag`枚举类保持一致 + */ +enum AccessibilityFeatures : int32_t { + AccessibleNavigation = 1 << 0, + InvertColors = 1 << 1, + DisableAnimations = 1 << 2, + BoldText = 1 << 3, + ReduceMotion = 1 << 4, + HighContrast = 1 << 5, + OnOffSwitchLabels = 1 << 6, }; - -} -#endif \ No newline at end of file +} // namespace flutter + +#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 index 9e636097658b3fe54ef80062b79e8fd5649ce1f8..e9b1499f702b7dd1a5adcb535340ca0d9b280b2c 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_manager.cpp +++ b/shell/platform/ohos/accessibility/ohos_accessibility_manager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,23 +12,27 @@ * 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" +#include "ohos_accessibility_manager.h" namespace flutter { -OhosAccessibilityManager::OhosAccessibilityManager() {}; -OhosAccessibilityManager::~OhosAccessibilityManager() {}; -void OhosAccessibilityManager::onAccessibilityStateChanged( - bool isOhosAccessibilityEnabled) {}; +OhosAccessibilityManager::OhosAccessibilityManager() {} -void OhosAccessibilityManager::setOhosAccessibilityEnabled(bool isEnabled) { - FML_DLOG(INFO) << "OhosAccessibilityManager::setOhosAccessibilityEnabled = " - << isEnabled; +OhosAccessibilityManager::~OhosAccessibilityManager() {} + +/** + * 监听ohos平台是否开启无障碍屏幕朗读功能 + */ +void OhosAccessibilityManager::OnAccessibilityStateChanged( + bool ohosAccessibilityEnabled) {} + +void OhosAccessibilityManager::SetOhosAccessibilityEnabled(bool isEnabled) +{ this->isOhosAccessibilityEnabled_ = isEnabled; } -bool OhosAccessibilityManager::getOhosAccessibilityEnabled() { +bool OhosAccessibilityManager::GetOhosAccessibilityEnabled() +{ return this->isOhosAccessibilityEnabled_; } diff --git a/shell/platform/ohos/accessibility/ohos_accessibility_manager.h b/shell/platform/ohos/accessibility/ohos_accessibility_manager.h index d32e76173e6a3fa363437c534e0467b01e5ce31f..3e37c59e0e4b005c9314e82209d34031ce7574a7 100644 --- a/shell/platform/ohos/accessibility/ohos_accessibility_manager.h +++ b/shell/platform/ohos/accessibility/ohos_accessibility_manager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. + * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,7 +14,7 @@ */ #ifndef OHOS_ACCESSIBILITY_MANAGER_H #define OHOS_ACCESSIBILITY_MANAGER_H - +#include namespace flutter { /** @@ -25,12 +25,13 @@ class OhosAccessibilityManager { OhosAccessibilityManager(); ~OhosAccessibilityManager(); - void onAccessibilityStateChanged(bool isOhosAccessibilityEnabled); - bool getOhosAccessibilityEnabled(); - void setOhosAccessibilityEnabled(bool isEnabled); + bool isOhosAccessibilityEnabled_; + + void OnAccessibilityStateChanged(bool ohosAccessibilityEnabled); + + bool GetOhosAccessibilityEnabled(); - private: - bool isOhosAccessibilityEnabled_ = false; + void SetOhosAccessibilityEnabled(bool isEnabled); }; } // namespace flutter 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 6786bedf54807514a6452c523f711ce2f2809a6c..3090d45f589eacc2aee148db1fae67dbefbe88a1 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 @@ -146,7 +146,7 @@ export const nativeSetTextureBufferSize: (nativeShellHolderId: number, textureId */ export const nativeSetAccessibilityFeatures: (accessibilityFeatureFlags: number, responseId: number) => void; -export const nativeAccessibilityStateChange: (state: Boolean) => void; +export const nativeAccessibilityStateChange: (nativeShellHolderId: number, state: Boolean) => void; export const nativeAnnounce: (message: string) => 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 e95bdfbf4b6748a951af0a2d0ef36198b626a2ea..741f5a82908cc0cb984e6b06da7972c24198364d 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 @@ -389,7 +389,7 @@ export default class FlutterNapi { this.accessibilityDelegate.accessibilityStateChange(state); } Log.d(TAG, "accessibilityStateChange is called"); - flutter.nativeAccessibilityStateChange(state); + flutter.nativeAccessibilityStateChange(this.nativeShellHolderId!, state); } setLocalizationPlugin(localizationPlugin: LocalizationPlugin | null): void { @@ -523,6 +523,7 @@ export default class FlutterNapi { } } + //native c++ get the shell holder id getShellHolderId(): void { this.ensureRunningOnMainThread(); if (this.isAttached()) { 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 8a336a529738df5ad8a30877b87cab08762b6058..cac6460d740d66d85a81cfd7dec70b996f89df83 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 @@ -37,7 +37,6 @@ 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'; @@ -53,7 +52,6 @@ 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; @@ -116,11 +114,9 @@ export class FlutterAbility extends UIAbility implements Host { //冷启动对os是否开启无障碍服务进行查询 let accessibilityState: boolean = accessibility.isOpenAccessibilitySync(); if(accessibilityState) { - this.delegate?.getFlutterNapi()?.setSemanticsEnabled(accessibilityState); + this.delegate?.getFlutterNapi()?.accessibilityStateChange(accessibilityState); } Log.i(TAG, `accessibility isOpen state -> ${JSON.stringify(accessibilityState)}`); - - this.accessibilityManager = new AccessibilityManager(); } onDestroy() { @@ -391,7 +387,7 @@ export class FlutterAbility extends UIAbility implements Host { .send(); //热启动生命周期内,实时监听系统设置环境改变并实时发送相应信息 //实时获取系统字体加粗系数 - this.delegate?.getFlutterNapi()?.setFontWeightScale(config.fontWeightScale == undefined? 0 : config.fontWeightScale); + this.delegate?.getFlutterNapi()?.setFontWeightScale(config.fontWeightScale == undefined? 1.0 : config.fontWeightScale); Log.i(TAG, 'fontWeightScale: ' + JSON.stringify(config.fontWeightScale)); if (config.language != '') { 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 6f682d78c22d4bda55141a0e7bb389c2de172444..800761001d626fed2a2082f73ec6f94b968f995b 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 @@ -154,7 +154,7 @@ export class FlutterView { //监听系统无障碍服务状态改变 accessibility.on('accessibilityStateChange', (data: boolean) => { Log.i(TAG, `subscribe accessibility state change, result: ${JSON.stringify(data)}`); - this.flutterEngine?.getFlutterNapi()?.setSemanticsEnabled(data); + this.flutterEngine?.getFlutterNapi()?.accessibilityStateChange(data); }); this.systemAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); this.navigationAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); diff --git a/shell/platform/ohos/napi/platform_view_ohos_napi.cpp b/shell/platform/ohos/napi/platform_view_ohos_napi.cpp index 18977911e54dacf1c157dca144cb8116c4959249..990cd80f886669ff72466a40a72071e3a9a27e2b 100644 --- a/shell/platform/ohos/napi/platform_view_ohos_napi.cpp +++ b/shell/platform/ohos/napi/platform_view_ohos_napi.cpp @@ -39,6 +39,7 @@ namespace flutter { napi_env PlatformViewOHOSNapi::env_; std::vector PlatformViewOHOSNapi::system_languages; +int64_t PlatformViewOHOSNapi::napi_shell_holder_id_; /** * @brief send empty PlatformMessage @@ -507,6 +508,15 @@ napi_value PlatformViewOHOSNapi::nativeAttach(napi_env env, } } +void PlatformViewOHOSNapi::GetShellHolderId() { + FML_DLOG(INFO) << "GetShellHolderId"; + napi_status status = fml::napi::InvokeJsMethod(env_, ref_napi_obj_, + "getShellHolderId", 0, nullptr); + if (status != napi_ok) { + FML_DLOG(ERROR) << "InvokeJsMethod getShellHolderId fail "; + } +} + /** * 加载dart工程构建产物 */ @@ -1836,6 +1846,7 @@ napi_value PlatformViewOHOSNapi::nativeUpdateCustomAccessibilityActions( return nullptr; } + /** * 监听获取系统的无障碍服务是否开启 */ @@ -1843,8 +1854,8 @@ napi_value PlatformViewOHOSNapi::nativeAccessibilityStateChange( napi_env env, napi_callback_info info) { napi_status ret; - size_t argc = 1; - napi_value args[1] = {nullptr}; + 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::nativeAccessibilityStateChange " @@ -1852,8 +1863,16 @@ napi_value PlatformViewOHOSNapi::nativeAccessibilityStateChange( << ret; return nullptr; } + int64_t shell_holder_id; + ret = napi_get_value_int64(env, args[0], &shell_holder_id); + if (ret != napi_ok) { + FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeAccessibilityStateChange " + "napi_get_value_int64 error:" + << ret; + return nullptr; + } bool state = false; - ret = napi_get_value_bool(env, args[0], &state); + ret = napi_get_value_bool(env, args[1], &state); if (ret != napi_ok) { FML_DLOG(ERROR) << "PlatformViewOHOSNapi::nativeAccessibilityStateChange " "napi_get_value_bool error:" @@ -1864,10 +1883,12 @@ napi_value PlatformViewOHOSNapi::nativeAccessibilityStateChange( "PlatformViewOHOSNapi::nativeAccessibilityStateChange state is: " "%{public}s", (state ? "true" : "false")); - // 传递到无障碍管理类 - auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); - ohosAccessibilityBridge->isOhosAccessibilityEnabled_ = state; + //send to accessibility bridge + auto a11y_bridge = OhosAccessibilityBridge::GetInstance(); + a11y_bridge->OnOhosAccessibilityStateChange(shell_holder_id, state); + FML_DLOG(INFO) << "nativeAccessibilityStateChange: state=" << state + << " shell_holder_id=" << shell_holder_id; return nullptr; } @@ -1886,8 +1907,8 @@ napi_value PlatformViewOHOSNapi::nativeAnnounce( 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); + auto handler = std::make_shared(); + handler->Announce(char_array); return nullptr; } @@ -1932,7 +1953,7 @@ napi_value PlatformViewOHOSNapi::nativeSetSemanticsEnabled(napi_env env, napi_ca //给无障碍bridge传递nativeShellHolderId auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); - ohosAccessibilityBridge->nativeShellHolder_ = shell_holder; + ohosAccessibilityBridge->native_shell_holder_id_ = shell_holder; FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetSemanticsEnabled -> shell_holder:"<SetBoldText(fontWeightScale, shell_holder); + auto accessibilityFeatures = std::make_shared(); + accessibilityFeatures->SetBoldText(fontWeightScale, shell_holder); FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeSetFontWeightScale -> shell_holder: " << shell_holder << " fontWeightScale: "<< fontWeightScale; @@ -1982,12 +2002,35 @@ napi_value PlatformViewOHOSNapi::nativeGetShellHolderId(napi_env env, napi_callb << ret; return nullptr; } - auto ohosAccessibilityBridge = OhosAccessibilityBridge::GetInstance(); - ohosAccessibilityBridge->nativeShellHolder_ = shell_holder; - FML_DLOG(INFO) << "PlatformViewOHOSNapi::nativeGetShellHolderId -> shell_holder:"< shell_holder:"<GetPlatformView()->SetSemanticsEnabled(enabled); +} + +void PlatformViewOHOSNapi::DispatchSemanticsAction( + int64_t shell_holder, + int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args) +{ + OHOS_SHELL_HOLDER->GetPlatformView()->PlatformView::DispatchSemanticsAction(id, action, fml::MallocMapping()); +} + +void PlatformViewOHOSNapi::SetAccessibilityFeatures(int64_t shell_holder, + int32_t flags) +{ + OHOS_SHELL_HOLDER->GetPlatformView()->SetAccessibilityFeatures(flags); +} + napi_value PlatformViewOHOSNapi::nativeLookupCallbackInformation(napi_env env, napi_callback_info info) { napi_value result; diff --git a/shell/platform/ohos/napi/platform_view_ohos_napi.h b/shell/platform/ohos/napi/platform_view_ohos_napi.h index f1aeac3bb53ca12012b309c96ab0c44352fd4fee..8c576f3403d9f2656f95474f5a9b9528746c4f54 100644 --- a/shell/platform/ohos/napi/platform_view_ohos_napi.h +++ b/shell/platform/ohos/napi/platform_view_ohos_napi.h @@ -28,7 +28,6 @@ #include "flutter/shell/common/run_configuration.h" #include "flutter/shell/platform/ohos/napi_common.h" #include "napi/native_api.h" -#include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_bridge.h" #include "flutter/shell/platform/ohos/accessibility/ohos_accessibility_features.h" // class for all c++ to call js function @@ -87,6 +86,16 @@ class PlatformViewOHOSNapi { void FlutterViewOnTouchEvent(std::shared_ptr touchPacketString, int size); + /** + * accessibility-relevant interfaces + */ + void GetShellHolderId(); + void SetSemanticsEnabled(int64_t shell_hoder, bool enabled); + void SetAccessibilityFeatures(int64_t shell_hoder, int32_t flags); + void DispatchSemanticsAction(int64_t shell_hoder, + int32_t id, + flutter::SemanticsAction action, + fml::MallocMapping args); static napi_value nativeUpdateRefreshRate( napi_env env, @@ -236,6 +245,7 @@ class PlatformViewOHOSNapi { napi_ref ref_napi_obj_; static std::vector system_languages; fml::RefPtr platform_task_runner_; + static int64_t napi_shell_holder_id_; }; } // namespace flutter diff --git a/shell/platform/ohos/platform_view_ohos.cpp b/shell/platform/ohos/platform_view_ohos.cpp index 212c3274ecf324cefaaf2ee291dc2915b8a79caa..9ed695758f7095127bb2508d0ab67d07cbc0aaec 100644 --- a/shell/platform/ohos/platform_view_ohos.cpp +++ b/shell/platform/ohos/platform_view_ohos.cpp @@ -25,7 +25,6 @@ #include "ohos_external_texture_gl.h" #include "ohos_logging.h" #include "flutter/shell/platform/ohos/platform_view_ohos_delegate.h" - #include namespace flutter { @@ -300,8 +299,8 @@ void PlatformViewOHOS::UpdateSemantics( flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { FML_DLOG(INFO) << "PlatformViewOHOS::UpdateSemantics is called"; - auto ax_bridge_delegate_ = OhosAccessibilityBridge::GetInstance(); - ax_bridge_delegate_->updateSemantics(update, actions); + auto nativeAccessibilityChannel_ = std::make_shared(); + nativeAccessibilityChannel_->UpdateSemantics(update, actions); } // |PlatformView| diff --git a/shell/platform/ohos/platform_view_ohos.h b/shell/platform/ohos/platform_view_ohos.h index dac7916885cb2bc574c4d6ee7c09b96be0cd4c2a..cca65a2fc5f4b1d5d9c423a60a8d3600c5dae33d 100644 --- a/shell/platform/ohos/platform_view_ohos.h +++ b/shell/platform/ohos/platform_view_ohos.h @@ -36,7 +36,8 @@ #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" +#include "flutter/shell/platform/ohos/accessibility/native_accessibility_channel.h" + namespace flutter { class OhosSurfaceFactoryImpl : public OhosSurfaceFactory { @@ -135,6 +136,9 @@ class PlatformViewOHOS final : public PlatformView { const std::shared_ptr napi_facade_; std::shared_ptr ohos_context_; + std::shared_ptr platform_view_ohos_delegate_; + NativeAccessibilityChannel nativeAccessibilityChannel_; + std::shared_ptr ohos_surface_; std::shared_ptr platform_message_handler_; @@ -142,8 +146,6 @@ 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();