1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/accessibility/accessibility_manager_ng.h"
17
18 #include "core/accessibility/accessibility_constants.h"
19 #include "core/accessibility/accessibility_session_adapter.h"
20 #include "core/components_ng/pattern/pattern.h"
21 #include "core/pipeline_ng/pipeline_context.h"
22
23 namespace OHOS::Ace::NG {
24 namespace {
GetOffsetToAncestorRevertTransform(const RefPtr<NG::FrameNode> & ancestor,const RefPtr<NG::FrameNode> & endNode,const PointF & pointAncestor,PointF & pointNode)25 void GetOffsetToAncestorRevertTransform(const RefPtr<NG::FrameNode>& ancestor, const RefPtr<NG::FrameNode>& endNode,
26 const PointF& pointAncestor, PointF& pointNode)
27 {
28 CHECK_NULL_VOID(ancestor);
29 CHECK_NULL_VOID(endNode);
30 auto context = endNode->GetRenderContext();
31 CHECK_NULL_VOID(context);
32 auto rect = context->GetPaintRectWithoutTransform();
33 OffsetF offset = rect.GetOffset();
34 VectorF finalScale {1.0f, 1.0f};
35 auto scale = endNode->GetTransformScale();
36 finalScale.x = scale.x;
37 finalScale.y = scale.y;
38
39 PointF ancestorLeftTopPoint(offset.GetX(), offset.GetY());
40 context->GetPointTransformRotate(ancestorLeftTopPoint);
41 auto parent = endNode->GetAncestorNodeOfFrame(true);
42 while (parent) {
43 auto parentRenderContext = parent->GetRenderContext();
44 if (parentRenderContext) {
45 offset = parentRenderContext->GetPaintRectWithoutTransform().GetOffset();
46 PointF pointTmp(offset.GetX() + ancestorLeftTopPoint.GetX(), offset.GetY() + ancestorLeftTopPoint.GetY());
47 parentRenderContext->GetPointTransformRotate(pointTmp);
48 ancestorLeftTopPoint.SetX(pointTmp.GetX());
49 ancestorLeftTopPoint.SetY(pointTmp.GetY());
50 auto scale = parent->GetTransformScale();
51 finalScale.x *= scale.x;
52 finalScale.y *= scale.y;
53 }
54
55 if (ancestor && (parent == ancestor)) {
56 break;
57 }
58
59 parent = parent->GetAncestorNodeOfFrame(true);
60 }
61
62 if ((NearEqual(finalScale.x, 1.0f) && NearEqual(finalScale.y, 1.0f)) ||
63 NearZero(finalScale.x) || NearZero(finalScale.y)) {
64 pointNode.SetX(pointAncestor.GetX() - ancestorLeftTopPoint.GetX());
65 pointNode.SetY(pointAncestor.GetY() - ancestorLeftTopPoint.GetY());
66 } else {
67 pointNode.SetX((pointAncestor.GetX() - ancestorLeftTopPoint.GetX()) / finalScale.x);
68 pointNode.SetY((pointAncestor.GetY() - ancestorLeftTopPoint.GetY()) / finalScale.y);
69 }
70 TAG_LOGD(AceLogTag::ACE_ACCESSIBILITY,
71 "GetOffsetToAncestorRevertTransform: offsetX %{public}f offsetY %{public}f scaleX %{public}f scaleY %{public}f",
72 pointNode.GetX(), pointNode.GetY(), finalScale.x, finalScale.y);
73 }
74
CheckAndSendHoverEnterByAncestor(const RefPtr<NG::FrameNode> & ancestor)75 void CheckAndSendHoverEnterByAncestor(const RefPtr<NG::FrameNode>& ancestor)
76 {
77 CHECK_NULL_VOID(ancestor);
78 auto pipeline = ancestor->GetContext();
79 CHECK_NULL_VOID(pipeline);
80 // Inter Process is showed as a component with rect like form process,
81 // need send hover enter when no component hovered to focus outside
82 if (pipeline->IsFormRender() || pipeline->IsJsCard() || pipeline->IsJsPlugin()) {
83 TAG_LOGD(AceLogTag::ACE_ACCESSIBILITY, "SendHoverEnterByAncestor");
84 ancestor->OnAccessibilityEvent(AccessibilityEventType::HOVER_ENTER_EVENT);
85 }
86 }
87
IsTouchExplorationEnabled(const RefPtr<FrameNode> & root)88 bool IsTouchExplorationEnabled(const RefPtr<FrameNode>& root)
89 {
90 CHECK_NULL_RETURN(root, true);
91 auto pipeline = root->GetContext();
92 CHECK_NULL_RETURN(pipeline, true);
93 auto jsAccessibilityManager = pipeline->GetAccessibilityManager();
94 CHECK_NULL_RETURN(jsAccessibilityManager, true);
95 auto accessibilityWorkMode = jsAccessibilityManager->GenerateAccessibilityWorkMode();
96 return accessibilityWorkMode.isTouchExplorationEnabled;
97 }
98 }
99
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,const MouseEvent & event)100 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, const MouseEvent& event)
101 {
102 if (root == nullptr ||
103 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
104 !IsTouchExplorationEnabled(root) ||
105 event.sourceType != SourceType::MOUSE) {
106 return;
107 }
108 AccessibilityHoverEventType type = AccessibilityHoverEventType::MOVE;
109 switch (event.action) {
110 case MouseAction::WINDOW_ENTER:
111 type = AccessibilityHoverEventType::ENTER;
112 break;
113 case MouseAction::MOVE:
114 type = AccessibilityHoverEventType::MOVE;
115 break;
116 case MouseAction::WINDOW_LEAVE:
117 type = AccessibilityHoverEventType::EXIT;
118 break;
119 default:
120 return;
121 }
122 PointF point(event.x, event.y);
123 HandleAccessibilityHoverEventInner(root, point, SourceType::MOUSE, type, event.time);
124 }
125
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,const TouchEvent & event)126 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, const TouchEvent& event)
127 {
128 if (root == nullptr ||
129 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
130 !IsTouchExplorationEnabled(root) ||
131 event.sourceType == SourceType::MOUSE) {
132 return;
133 }
134 AccessibilityHoverEventType type = AccessibilityHoverEventType::MOVE;
135 switch (event.type) {
136 case TouchType::HOVER_ENTER:
137 type = AccessibilityHoverEventType::ENTER;
138 break;
139 case TouchType::HOVER_MOVE:
140 type = AccessibilityHoverEventType::MOVE;
141 break;
142 case TouchType::HOVER_EXIT:
143 type = AccessibilityHoverEventType::EXIT;
144 break;
145 default:
146 return;
147 }
148 PointF point(event.x, event.y);
149 if (event.pointers.size() > 1 && event.sourceType == SourceType::TOUCH) {
150 if (hoverState_.source == SourceType::TOUCH) {
151 ResetHoverState();
152 return;
153 }
154 }
155 HandleAccessibilityHoverEventInner(root, point, event.sourceType, type, event.time);
156 }
157
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,float pointX,float pointY,int32_t sourceType,int32_t eventType,int64_t timeMs)158 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, float pointX, float pointY,
159 int32_t sourceType, int32_t eventType, int64_t timeMs)
160 {
161 if (root == nullptr ||
162 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
163 !IsTouchExplorationEnabled(root) ||
164 eventType < 0 || eventType >= static_cast<int32_t>(AccessibilityHoverEventType::Count)) {
165 return;
166 }
167 PointF point(pointX, pointY);
168 TimeStamp time((std::chrono::milliseconds(timeMs)));
169
170 if (IsHandlePipelineAccessibilityHoverEnter(root)) {
171 TouchEvent event;
172 event.x = pointX;
173 event.y = pointY;
174 event.sourceType = static_cast<SourceType>(sourceType);
175 event.time = time;
176 HandlePipelineAccessibilityHoverEnter(root, event, eventType);
177 } else {
178 HandleAccessibilityHoverEventInner(root, point, static_cast<SourceType>(sourceType),
179 static_cast<AccessibilityHoverEventType>(eventType), time);
180 }
181 }
182
HandleAccessibilityHoverEventInner(const RefPtr<FrameNode> & root,const PointF & point,SourceType sourceType,AccessibilityHoverEventType eventType,TimeStamp time)183 void AccessibilityManagerNG::HandleAccessibilityHoverEventInner(
184 const RefPtr<FrameNode>& root,
185 const PointF& point,
186 SourceType sourceType,
187 AccessibilityHoverEventType eventType,
188 TimeStamp time)
189 {
190 static constexpr size_t THROTTLE_INTERVAL_HOVER_EVENT = 100;
191 uint64_t duration =
192 static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(time - hoverState_.time).count());
193 if (!hoverState_.idle) {
194 if ((!IsEventTypeChangeDirectHandleHover(eventType)) && (duration < THROTTLE_INTERVAL_HOVER_EVENT)) {
195 return;
196 }
197 }
198
199 static constexpr size_t MIN_SOURCE_CHANGE_GAP_MS = 1000;
200 if (sourceType != hoverState_.source && !hoverState_.idle) {
201 if (duration < MIN_SOURCE_CHANGE_GAP_MS) {
202 return;
203 }
204 ResetHoverState();
205 }
206
207 ACE_SCOPED_TRACE("HandleAccessibilityHoverEventInner");
208 if (eventType == AccessibilityHoverEventType::ENTER) {
209 ResetHoverState();
210 }
211 std::vector<WeakPtr<FrameNode>> currentNodesHovering;
212 std::vector<RefPtr<FrameNode>> lastNodesHovering;
213 std::vector<int32_t> lastNodesHoveringId;
214 for (const auto& nodeWeak: hoverState_.nodesHovering) {
215 auto node = nodeWeak.Upgrade();
216 if (node != nullptr) {
217 lastNodesHovering.push_back(node);
218 lastNodesHoveringId.push_back(node->GetId());
219 }
220 }
221 if (eventType != AccessibilityHoverEventType::EXIT) {
222 std::unique_ptr<AccessibilityProperty::HoverTestDebugTraceInfo> debugInfo = nullptr;
223 AccessibilityHoverTestPath path = AccessibilityProperty::HoverTest(point, root, debugInfo);
224 for (const auto& node: path) {
225 auto id = node->GetId();
226 if (std::find(lastNodesHoveringId.begin(), lastNodesHoveringId.end(), id) != lastNodesHoveringId.end() ||
227 AccessibilityProperty::IsAccessibilityFocusable(node)) {
228 currentNodesHovering.push_back(node);
229 }
230 }
231 }
232 auto sendHoverEnter = false;
233 static constexpr int32_t INVALID_NODE_ID = -1;
234 int32_t lastHoveringId = INVALID_NODE_ID;
235 RefPtr<FrameNode> lastHovering = nullptr;
236 if (!lastNodesHovering.empty()) {
237 lastHovering = lastNodesHovering.back();
238 lastHoveringId = lastHovering->GetId();
239 }
240 int32_t currentHoveringId = INVALID_NODE_ID;
241 RefPtr<FrameNode> currentHovering = nullptr;
242 if (!currentNodesHovering.empty()) {
243 currentHovering = currentNodesHovering.back().Upgrade();
244 currentHoveringId = currentHovering->GetId();
245 }
246 if (!DeliverAccessibilityHoverEvent(currentHovering, point)) {
247 if (lastHoveringId != INVALID_NODE_ID && lastHoveringId != currentHoveringId) {
248 lastHovering->OnAccessibilityEvent(AccessibilityEventType::HOVER_EXIT_EVENT);
249 NotifyHoverEventToNodeSession(lastHovering, root, point,
250 sourceType, AccessibilityHoverEventType::EXIT, time);
251 }
252 if (currentHoveringId != INVALID_NODE_ID) {
253 if (currentHoveringId != lastHoveringId && (!IgnoreCurrentHoveringNode(currentHovering))) {
254 currentHovering->OnAccessibilityEvent(AccessibilityEventType::HOVER_ENTER_EVENT);
255 sendHoverEnter = true;
256 }
257 NotifyHoverEventToNodeSession(currentHovering, root, point,
258 sourceType, eventType, time);
259 }
260
261 if (!sendHoverEnter && (eventType == AccessibilityHoverEventType::ENTER)) {
262 // check need send hover enter when no component hovered to focus outside
263 CheckAndSendHoverEnterByAncestor(root);
264 }
265 }
266
267 hoverState_.nodesHovering = std::move(currentNodesHovering);
268 hoverState_.time = time;
269 hoverState_.source = sourceType;
270 hoverState_.idle = eventType == AccessibilityHoverEventType::EXIT;
271 hoverState_.eventType = eventType;
272 }
273
DeliverAccessibilityHoverEvent(const RefPtr<FrameNode> & hoverNode,const PointF & point)274 bool AccessibilityManagerNG::DeliverAccessibilityHoverEvent(const RefPtr<FrameNode>& hoverNode, const PointF& point)
275 {
276 CHECK_NULL_RETURN(hoverNode, false);
277 auto hoverNodePattern = hoverNode->GetPattern();
278 CHECK_NULL_RETURN(hoverNodePattern, false);
279 return hoverNodePattern->OnAccessibilityHoverEvent(point);
280 }
281
IgnoreCurrentHoveringNode(const RefPtr<FrameNode> & node)282 bool AccessibilityManagerNG::IgnoreCurrentHoveringNode(const RefPtr<FrameNode> &node)
283 {
284 auto sessionAdapter = AccessibilitySessionAdapter::GetSessionAdapter(node);
285 CHECK_NULL_RETURN(sessionAdapter, false);
286 return sessionAdapter->IgnoreHostNode();
287 }
288
NotifyHoverEventToNodeSession(const RefPtr<FrameNode> & node,const RefPtr<FrameNode> & rootNode,const PointF & pointRoot,SourceType sourceType,AccessibilityHoverEventType eventType,TimeStamp time)289 void AccessibilityManagerNG::NotifyHoverEventToNodeSession(const RefPtr<FrameNode>& node,
290 const RefPtr<FrameNode>& rootNode, const PointF& pointRoot,
291 SourceType sourceType, AccessibilityHoverEventType eventType, TimeStamp time)
292 {
293 auto eventHub = node->GetEventHub<EventHub>();
294 if (!eventHub->IsEnabled()) {
295 // If the host component is disabled, do not transfer hover event.
296 return;
297 }
298 auto sessionAdapter = AccessibilitySessionAdapter::GetSessionAdapter(node);
299 CHECK_NULL_VOID(sessionAdapter);
300 PointF pointNode(pointRoot);
301 if (AccessibilityManagerNG::ConvertPointFromAncestorToNode(rootNode, node, pointRoot, pointNode)) {
302 sessionAdapter->TransferHoverEvent(pointNode, sourceType, eventType, time);
303 }
304 }
305
ResetHoverState()306 void AccessibilityManagerNG::ResetHoverState()
307 {
308 hoverState_.idle = true;
309 hoverState_.nodesHovering.clear();
310 }
311
HoverTestDebug(const RefPtr<FrameNode> & root,const PointF & point,std::string & summary,std::string & detail) const312 void AccessibilityManagerNG::HoverTestDebug(const RefPtr<FrameNode>& root, const PointF& point,
313 std::string& summary, std::string& detail) const
314 {
315 auto summaryJson = JsonUtil::Create();
316 auto detailJson = JsonUtil::Create();
317 std::stringstream summaryNodesSearched;
318 auto debugInfo = std::make_unique<AccessibilityProperty::HoverTestDebugTraceInfo>();
319 AccessibilityHoverTestPath path = AccessibilityProperty::HoverTest(point, root, debugInfo);
320 auto summaryPath = JsonUtil::CreateArray();
321 auto summarySelected = JsonUtil::CreateArray();
322
323 auto detaiSelectionInfo = JsonUtil::CreateArray();
324 size_t numNodesSelected = 0;
325 for (size_t i = 0; i < path.size(); ++i) {
326 summaryPath->Put(std::to_string(i).c_str(), path[i]->GetAccessibilityId());
327 auto detailNodeSelection = JsonUtil::Create();
328 if (AccessibilityProperty::IsAccessibilityFocusableDebug(path[i], detailNodeSelection)) {
329 summarySelected->Put(std::to_string(numNodesSelected).c_str(), path[i]->GetAccessibilityId());
330 ++numNodesSelected;
331 }
332 detaiSelectionInfo->PutRef(std::move(detailNodeSelection));
333 }
334 summaryJson->PutRef("path", std::move(summaryPath));
335 summaryJson->PutRef("nodesSelected", std::move(summarySelected));
336
337 auto detailSearchInfo = JsonUtil::CreateArray();
338 for (size_t i = 0; i < debugInfo->trace.size(); ++i) {
339 auto detailNodeSearch = std::move(debugInfo->trace[i]);
340 detailSearchInfo->Put(std::to_string(i).c_str(), detailNodeSearch);
341 }
342 detailJson->PutRef("detailSearch", std::move(detailSearchInfo));
343 detailJson->PutRef("detailSelection", std::move(detaiSelectionInfo));
344 summary = summaryJson->ToString();
345 detail = detailJson->ToString();
346 }
347
ConvertPointFromAncestorToNode(const RefPtr<NG::FrameNode> & ancestor,const RefPtr<NG::FrameNode> & endNode,const PointF & pointAncestor,PointF & pointNode)348 bool AccessibilityManagerNG::ConvertPointFromAncestorToNode(
349 const RefPtr<NG::FrameNode>& ancestor, const RefPtr<NG::FrameNode>& endNode,
350 const PointF& pointAncestor, PointF& pointNode)
351 {
352 CHECK_NULL_RETURN(ancestor, false);
353 CHECK_NULL_RETURN(endNode, false);
354 // revert scale from endNode to ancestor
355 GetOffsetToAncestorRevertTransform(ancestor, endNode, pointAncestor, pointNode);
356 return true;
357 }
358
IsEventTypeChangeDirectHandleHover(AccessibilityHoverEventType eventType) const359 bool AccessibilityManagerNG::IsEventTypeChangeDirectHandleHover(AccessibilityHoverEventType eventType) const
360 {
361 if ((hoverState_.eventType == AccessibilityHoverEventType::MOVE)
362 && (eventType == AccessibilityHoverEventType::EXIT)) {
363 return true;
364 }
365 return false;
366 }
367
IsHandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode> & root) const368 bool AccessibilityManagerNG::IsHandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode>& root) const
369 {
370 auto pipeline = root->GetContext();
371 CHECK_NULL_RETURN(pipeline, false);
372 auto ngPipeline = AceType::DynamicCast<NG::PipelineContext>(pipeline);
373 CHECK_NULL_RETURN(ngPipeline, false);
374
375 auto container = Container::GetContainer(ngPipeline->GetInstanceId());
376 if (container && (container->IsUIExtensionWindow())) {
377 return true;
378 }
379 return false;
380 }
381
HandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode> & root,TouchEvent & event,int32_t eventType)382 void AccessibilityManagerNG::HandlePipelineAccessibilityHoverEnter(
383 const RefPtr<NG::FrameNode>& root,
384 TouchEvent& event,
385 int32_t eventType)
386 {
387 CHECK_NULL_VOID(root);
388 AccessibilityHoverEventType eventHoverType = static_cast<AccessibilityHoverEventType>(eventType);
389 event.type = TouchType::HOVER_MOVE;
390 switch (eventHoverType) {
391 case AccessibilityHoverEventType::ENTER:
392 event.type = TouchType::HOVER_ENTER;
393 break;
394 case AccessibilityHoverEventType::MOVE:
395 event.type = TouchType::HOVER_MOVE;
396 break;
397 case AccessibilityHoverEventType::EXIT:
398 event.type = TouchType::HOVER_EXIT;
399 break;
400 default:
401 break;
402 }
403
404 auto pipeline = root->GetContext();
405 CHECK_NULL_VOID(pipeline);
406 pipeline->OnAccessibilityHoverEvent(event, root);
407 }
408 } // namespace OHOS::Ace::NG
409