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 }
88
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,const MouseEvent & event)89 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, const MouseEvent& event)
90 {
91 if (root == nullptr ||
92 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
93 event.sourceType != SourceType::MOUSE) {
94 return;
95 }
96 AccessibilityHoverEventType type = AccessibilityHoverEventType::MOVE;
97 switch (event.action) {
98 case MouseAction::WINDOW_ENTER:
99 type = AccessibilityHoverEventType::ENTER;
100 break;
101 case MouseAction::MOVE:
102 type = AccessibilityHoverEventType::MOVE;
103 break;
104 case MouseAction::WINDOW_LEAVE:
105 type = AccessibilityHoverEventType::EXIT;
106 break;
107 default:
108 return;
109 }
110 PointF point(event.x, event.y);
111 HandleAccessibilityHoverEventInner(root, point, SourceType::MOUSE, type, event.time);
112 }
113
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,const TouchEvent & event)114 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, const TouchEvent& event)
115 {
116 if (root == nullptr ||
117 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
118 event.sourceType == SourceType::MOUSE) {
119 return;
120 }
121 AccessibilityHoverEventType type = AccessibilityHoverEventType::MOVE;
122 switch (event.type) {
123 case TouchType::HOVER_ENTER:
124 type = AccessibilityHoverEventType::ENTER;
125 break;
126 case TouchType::HOVER_MOVE:
127 type = AccessibilityHoverEventType::MOVE;
128 break;
129 case TouchType::HOVER_EXIT:
130 type = AccessibilityHoverEventType::EXIT;
131 break;
132 default:
133 return;
134 }
135 PointF point(event.x, event.y);
136 if (event.pointers.size() > 1 && event.sourceType == SourceType::TOUCH) {
137 if (hoverState_.source == SourceType::TOUCH) {
138 ResetHoverState();
139 return;
140 }
141 }
142 HandleAccessibilityHoverEventInner(root, point, event.sourceType, type, event.time);
143 }
144
HandleAccessibilityHoverEvent(const RefPtr<FrameNode> & root,float pointX,float pointY,int32_t sourceType,int32_t eventType,int64_t timeMs)145 void AccessibilityManagerNG::HandleAccessibilityHoverEvent(const RefPtr<FrameNode>& root, float pointX, float pointY,
146 int32_t sourceType, int32_t eventType, int64_t timeMs)
147 {
148 if (root == nullptr ||
149 !AceApplicationInfo::GetInstance().IsAccessibilityEnabled() ||
150 eventType < 0 || eventType >= static_cast<int32_t>(AccessibilityHoverEventType::Count)) {
151 return;
152 }
153 PointF point(pointX, pointY);
154 TimeStamp time((std::chrono::milliseconds(timeMs)));
155
156 if (IsHandlePipelineAccessibilityHoverEnter(root)) {
157 TouchEvent event;
158 event.x = pointX;
159 event.y = pointY;
160 event.sourceType = static_cast<SourceType>(sourceType);
161 event.time = time;
162 HandlePipelineAccessibilityHoverEnter(root, event, eventType);
163 } else {
164 HandleAccessibilityHoverEventInner(root, point, static_cast<SourceType>(sourceType),
165 static_cast<AccessibilityHoverEventType>(eventType), time);
166 }
167 }
168
HandleAccessibilityHoverEventInner(const RefPtr<FrameNode> & root,const PointF & point,SourceType sourceType,AccessibilityHoverEventType eventType,TimeStamp time)169 void AccessibilityManagerNG::HandleAccessibilityHoverEventInner(
170 const RefPtr<FrameNode>& root,
171 const PointF& point,
172 SourceType sourceType,
173 AccessibilityHoverEventType eventType,
174 TimeStamp time)
175 {
176 static constexpr size_t THROTTLE_INTERVAL_HOVER_EVENT = 100;
177 uint64_t duration =
178 static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(time - hoverState_.time).count());
179 if (!hoverState_.idle) {
180 if ((!IsEventTypeChangeDirectHandleHover(eventType)) && (duration < THROTTLE_INTERVAL_HOVER_EVENT)) {
181 return;
182 }
183 }
184
185 static constexpr size_t MIN_SOURCE_CHANGE_GAP_MS = 1000;
186 if (sourceType != hoverState_.source && !hoverState_.idle) {
187 if (duration < MIN_SOURCE_CHANGE_GAP_MS) {
188 return;
189 }
190 ResetHoverState();
191 }
192
193 ACE_SCOPED_TRACE("HandleAccessibilityHoverEventInner");
194 if (eventType == AccessibilityHoverEventType::ENTER) {
195 ResetHoverState();
196 }
197 std::vector<WeakPtr<FrameNode>> currentNodesHovering;
198 std::vector<RefPtr<FrameNode>> lastNodesHovering;
199 std::vector<int32_t> lastNodesHoveringId;
200 for (const auto& nodeWeak: hoverState_.nodesHovering) {
201 auto node = nodeWeak.Upgrade();
202 if (node != nullptr) {
203 lastNodesHovering.push_back(node);
204 lastNodesHoveringId.push_back(node->GetId());
205 }
206 }
207 if (eventType != AccessibilityHoverEventType::EXIT) {
208 std::unique_ptr<AccessibilityProperty::HoverTestDebugTraceInfo> debugInfo = nullptr;
209 AccessibilityHoverTestPath path = AccessibilityProperty::HoverTest(point, root, debugInfo);
210 for (const auto& node: path) {
211 auto id = node->GetId();
212 if (std::find(lastNodesHoveringId.begin(), lastNodesHoveringId.end(), id) != lastNodesHoveringId.end() ||
213 AccessibilityProperty::IsAccessibilityFocusable(node)) {
214 currentNodesHovering.push_back(node);
215 }
216 }
217 }
218 auto sendHoverEnter = false;
219 static constexpr int32_t INVALID_NODE_ID = -1;
220 int32_t lastHoveringId = INVALID_NODE_ID;
221 RefPtr<FrameNode> lastHovering = nullptr;
222 if (!lastNodesHovering.empty()) {
223 lastHovering = lastNodesHovering.back();
224 lastHoveringId = lastHovering->GetId();
225 }
226 int32_t currentHoveringId = INVALID_NODE_ID;
227 RefPtr<FrameNode> currentHovering = nullptr;
228 if (!currentNodesHovering.empty()) {
229 currentHovering = currentNodesHovering.back().Upgrade();
230 currentHoveringId = currentHovering->GetId();
231 }
232 if (!DeliverAccessibilityHoverEvent(currentHovering, point)) {
233 if (lastHoveringId != INVALID_NODE_ID && lastHoveringId != currentHoveringId) {
234 lastHovering->OnAccessibilityEvent(AccessibilityEventType::HOVER_EXIT_EVENT);
235 NotifyHoverEventToNodeSession(lastHovering, root, point,
236 sourceType, AccessibilityHoverEventType::EXIT, time);
237 }
238 if (currentHoveringId != INVALID_NODE_ID) {
239 if (currentHoveringId != lastHoveringId && (!IgnoreCurrentHoveringNode(currentHovering))) {
240 currentHovering->OnAccessibilityEvent(AccessibilityEventType::HOVER_ENTER_EVENT);
241 sendHoverEnter = true;
242 }
243 NotifyHoverEventToNodeSession(currentHovering, root, point,
244 sourceType, eventType, time);
245 }
246
247 if (!sendHoverEnter && (eventType == AccessibilityHoverEventType::ENTER)) {
248 // check need send hover enter when no component hovered to focus outside
249 CheckAndSendHoverEnterByAncestor(root);
250 }
251 }
252
253 hoverState_.nodesHovering = std::move(currentNodesHovering);
254 hoverState_.time = time;
255 hoverState_.source = sourceType;
256 hoverState_.idle = eventType == AccessibilityHoverEventType::EXIT;
257 hoverState_.eventType = eventType;
258 }
259
DeliverAccessibilityHoverEvent(const RefPtr<FrameNode> & hoverNode,const PointF & point)260 bool AccessibilityManagerNG::DeliverAccessibilityHoverEvent(const RefPtr<FrameNode>& hoverNode, const PointF& point)
261 {
262 CHECK_NULL_RETURN(hoverNode, false);
263 auto hoverNodePattern = hoverNode->GetPattern();
264 CHECK_NULL_RETURN(hoverNodePattern, false);
265 return hoverNodePattern->OnAccessibilityHoverEvent(point);
266 }
267
IgnoreCurrentHoveringNode(const RefPtr<FrameNode> & node)268 bool AccessibilityManagerNG::IgnoreCurrentHoveringNode(const RefPtr<FrameNode> &node)
269 {
270 auto sessionAdapter = AccessibilitySessionAdapter::GetSessionAdapter(node);
271 CHECK_NULL_RETURN(sessionAdapter, false);
272 return sessionAdapter->IgnoreHostNode();
273 }
274
NotifyHoverEventToNodeSession(const RefPtr<FrameNode> & node,const RefPtr<FrameNode> & rootNode,const PointF & pointRoot,SourceType sourceType,AccessibilityHoverEventType eventType,TimeStamp time)275 void AccessibilityManagerNG::NotifyHoverEventToNodeSession(const RefPtr<FrameNode>& node,
276 const RefPtr<FrameNode>& rootNode, const PointF& pointRoot,
277 SourceType sourceType, AccessibilityHoverEventType eventType, TimeStamp time)
278 {
279 auto eventHub = node->GetEventHub<EventHub>();
280 if (!eventHub->IsEnabled()) {
281 // If the host component is disabled, do not transfer hover event.
282 return;
283 }
284 auto sessionAdapter = AccessibilitySessionAdapter::GetSessionAdapter(node);
285 CHECK_NULL_VOID(sessionAdapter);
286 PointF pointNode(pointRoot);
287 if (AccessibilityManagerNG::ConvertPointFromAncestorToNode(rootNode, node, pointRoot, pointNode)) {
288 sessionAdapter->TransferHoverEvent(pointNode, sourceType, eventType, time);
289 }
290 }
291
ResetHoverState()292 void AccessibilityManagerNG::ResetHoverState()
293 {
294 hoverState_.idle = true;
295 hoverState_.nodesHovering.clear();
296 }
297
HoverTestDebug(const RefPtr<FrameNode> & root,const PointF & point,std::string & summary,std::string & detail) const298 void AccessibilityManagerNG::HoverTestDebug(const RefPtr<FrameNode>& root, const PointF& point,
299 std::string& summary, std::string& detail) const
300 {
301 auto summaryJson = JsonUtil::Create();
302 auto detailJson = JsonUtil::Create();
303 std::stringstream summaryNodesSearched;
304 auto debugInfo = std::make_unique<AccessibilityProperty::HoverTestDebugTraceInfo>();
305 AccessibilityHoverTestPath path = AccessibilityProperty::HoverTest(point, root, debugInfo);
306 auto summaryPath = JsonUtil::CreateArray();
307 auto summarySelected = JsonUtil::CreateArray();
308
309 auto detaiSelectionInfo = JsonUtil::CreateArray();
310 size_t numNodesSelected = 0;
311 for (size_t i = 0; i < path.size(); ++i) {
312 summaryPath->Put(std::to_string(i).c_str(), path[i]->GetAccessibilityId());
313 auto detailNodeSelection = JsonUtil::Create();
314 if (AccessibilityProperty::IsAccessibilityFocusableDebug(path[i], detailNodeSelection)) {
315 summarySelected->Put(std::to_string(numNodesSelected).c_str(), path[i]->GetAccessibilityId());
316 ++numNodesSelected;
317 }
318 detaiSelectionInfo->PutRef(std::move(detailNodeSelection));
319 }
320 summaryJson->PutRef("path", std::move(summaryPath));
321 summaryJson->PutRef("nodesSelected", std::move(summarySelected));
322
323 auto detailSearchInfo = JsonUtil::CreateArray();
324 for (size_t i = 0; i < debugInfo->trace.size(); ++i) {
325 auto detailNodeSearch = std::move(debugInfo->trace[i]);
326 detailSearchInfo->Put(std::to_string(i).c_str(), detailNodeSearch);
327 }
328 detailJson->PutRef("detailSearch", std::move(detailSearchInfo));
329 detailJson->PutRef("detailSelection", std::move(detaiSelectionInfo));
330 summary = summaryJson->ToString();
331 detail = detailJson->ToString();
332 }
333
ConvertPointFromAncestorToNode(const RefPtr<NG::FrameNode> & ancestor,const RefPtr<NG::FrameNode> & endNode,const PointF & pointAncestor,PointF & pointNode)334 bool AccessibilityManagerNG::ConvertPointFromAncestorToNode(
335 const RefPtr<NG::FrameNode>& ancestor, const RefPtr<NG::FrameNode>& endNode,
336 const PointF& pointAncestor, PointF& pointNode)
337 {
338 CHECK_NULL_RETURN(ancestor, false);
339 CHECK_NULL_RETURN(endNode, false);
340 // revert scale from endNode to ancestor
341 GetOffsetToAncestorRevertTransform(ancestor, endNode, pointAncestor, pointNode);
342 return true;
343 }
344
IsEventTypeChangeDirectHandleHover(AccessibilityHoverEventType eventType) const345 bool AccessibilityManagerNG::IsEventTypeChangeDirectHandleHover(AccessibilityHoverEventType eventType) const
346 {
347 if ((hoverState_.eventType == AccessibilityHoverEventType::MOVE)
348 && (eventType == AccessibilityHoverEventType::EXIT)) {
349 return true;
350 }
351 return false;
352 }
353
IsHandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode> & root) const354 bool AccessibilityManagerNG::IsHandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode>& root) const
355 {
356 auto pipeline = root->GetContext();
357 CHECK_NULL_RETURN(pipeline, false);
358 auto ngPipeline = AceType::DynamicCast<NG::PipelineContext>(pipeline);
359 CHECK_NULL_RETURN(ngPipeline, false);
360
361 auto container = Container::GetContainer(ngPipeline->GetInstanceId());
362 if (container && (container->IsUIExtensionWindow())) {
363 return true;
364 }
365 return false;
366 }
367
HandlePipelineAccessibilityHoverEnter(const RefPtr<NG::FrameNode> & root,TouchEvent & event,int32_t eventType)368 void AccessibilityManagerNG::HandlePipelineAccessibilityHoverEnter(
369 const RefPtr<NG::FrameNode>& root,
370 TouchEvent& event,
371 int32_t eventType)
372 {
373 CHECK_NULL_VOID(root);
374 AccessibilityHoverEventType eventHoverType = static_cast<AccessibilityHoverEventType>(eventType);
375 event.type = TouchType::HOVER_MOVE;
376 switch (eventHoverType) {
377 case AccessibilityHoverEventType::ENTER:
378 event.type = TouchType::HOVER_ENTER;
379 break;
380 case AccessibilityHoverEventType::MOVE:
381 event.type = TouchType::HOVER_MOVE;
382 break;
383 case AccessibilityHoverEventType::EXIT:
384 event.type = TouchType::HOVER_EXIT;
385 break;
386 default:
387 break;
388 }
389
390 auto pipeline = root->GetContext();
391 CHECK_NULL_VOID(pipeline);
392 pipeline->OnAccessibilityHoverEvent(event, root);
393 }
394 } // namespace OHOS::Ace::NG
395