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