• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 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_node.h"
17 
18 #include "base/json/json_util.h"
19 #include "base/log/ace_trace.h"
20 #include "base/log/log.h"
21 #include "base/utils/linear_map.h"
22 #include "base/utils/utils.h"
23 #include "core/accessibility/accessibility_utils.h"
24 #include "core/common/container.h"
25 #include "core/event/ace_event_helper.h"
26 
27 namespace OHOS::Ace {
28 namespace {
29 
30 const char ACCESSIBILITY_VALUE[] = "value";
31 const char ACCESSIBILITY_TYPE[] = "type";
32 const char ACCESSIBILITY_DISABLED[] = "disabled";
33 const char ACCESSIBILITY_GROUP[] = "accessibilitygroup";
34 const char ACCESSIBILITY_TEXT[] = "accessibilitytext";
35 const char ACCESSIBILITY_DESCRIPTION[] = "accessibilitydescription";
36 const char ACCESSIBILITY_IMPORTANCE[] = "accessibilityimportance";
37 const char ACCESSIBILITY_SHOW[] = "show";
38 const char ID[] = "id";
39 
40 const char INPUT_TYPE_CHECKBOX[] = "checkbox";
41 const char INPUT_TYPE_RADIO[] = "radio";
42 const char INPUT_TYPE_PASSWORD[] = "password";
43 
44 // common event definition
45 const char ACCESSIBILITY_EVENT[] = "accessibility";
46 const char CLICK[] = "click";
47 const char LONG_PRESS[] = "longpress";
48 const char FOCUS[] = "focus";
49 const char BLUR[] = "blur";
50 
StringToBool(const std::string & value)51 inline bool StringToBool(const std::string& value)
52 {
53     return value == "true";
54 }
55 
MergeItems(const std::vector<std::pair<std::string,std::string>> & newItems,std::vector<std::pair<std::string,std::string>> & items)56 void MergeItems(const std::vector<std::pair<std::string, std::string>>& newItems,
57     std::vector<std::pair<std::string, std::string>>& items)
58 {
59     if (items.empty()) {
60         items = newItems;
61     } else {
62         std::unordered_map<std::string, std::string> originItems;
63         std::vector<std::pair<std::string, std::string>> sameVec;
64         std::vector<std::pair<std::string, std::string>> diffVec;
65         for (const auto& item: items) {
66             originItems[item.first] = item.second;
67         }
68 
69         for (const auto& item: newItems) {
70             originItems[item.first] = item.second;
71         }
72 
73         // find same element
74         for (auto item = items.begin(); item != items.end(); item++) {
75             const auto iter = originItems.find(item->first);
76             if (iter != originItems.end()) {
77                 sameVec.emplace_back(iter->first, iter->second);
78             }
79         }
80 
81         // find different element
82         for (auto newItem = newItems.begin(); newItem != newItems.end(); newItem++) {
83             size_t diffFlagCount = 0;
84             for (auto item = items.begin(); item != items.end(); item++) {
85                 if (newItem->first != item->first) {
86                     diffFlagCount++;
87                 }
88             }
89             if (diffFlagCount == items.size()) {
90                 diffVec.emplace_back(newItem->first, newItem->second);
91             }
92         }
93         sameVec.insert(sameVec.end(), diffVec.begin(), diffVec.end());
94         items.clear();
95         items = sameVec;
96     }
97 }
98 
99 } // namespace
100 
AccessibilityNode(NodeId nodeId,const std::string & nodeName)101 AccessibilityNode::AccessibilityNode(NodeId nodeId, const std::string& nodeName) : nodeId_(nodeId), tag_(nodeName)
102 {
103     // Initialize member variable in bitfield
104     isEnabled_ = true;
105     visible_ = true;
106     shown_ = true;
107 }
108 
SetActionClickImpl(const ActionClickImpl & actionClickImpl)109 void AccessibilityNode::SetActionClickImpl(const ActionClickImpl& actionClickImpl)
110 {
111     actionClickImpl_ = actionClickImpl;
112 }
113 
ActionClick()114 bool AccessibilityNode::ActionClick()
115 {
116     if (actionClickImpl_) {
117         actionClickImpl_();
118         return true;
119     }
120     return false;
121 }
122 
SetActionLongClickImpl(const ActionLongClickImpl & actionLongClickImpl)123 void AccessibilityNode::SetActionLongClickImpl(const ActionLongClickImpl& actionLongClickImpl)
124 {
125     actionLongClickImpl_ = actionLongClickImpl;
126 }
127 
ActionLongClick()128 bool AccessibilityNode::ActionLongClick()
129 {
130     if (actionLongClickImpl_) {
131         actionLongClickImpl_();
132         return true;
133     }
134     return false;
135 }
136 
SetActionScrollForward(const ActionScrollForwardImpl & actionScrollForwardImpl)137 void AccessibilityNode::SetActionScrollForward(const ActionScrollForwardImpl& actionScrollForwardImpl)
138 {
139     actionScrollForwardImpl_ = actionScrollForwardImpl;
140 }
141 
ActionScrollForward()142 bool AccessibilityNode::ActionScrollForward()
143 {
144     if (actionScrollForwardImpl_) {
145         return actionScrollForwardImpl_();
146     }
147     return false;
148 }
149 
SetActionScrollBackward(const ActionScrollBackwardImpl & actionScrollBackwardImpl)150 void AccessibilityNode::SetActionScrollBackward(const ActionScrollBackwardImpl& actionScrollBackwardImpl)
151 {
152     actionScrollBackwardImpl_ = actionScrollBackwardImpl;
153 }
154 
ActionScrollBackward()155 bool AccessibilityNode::ActionScrollBackward()
156 {
157     if (actionScrollBackwardImpl_) {
158         return actionScrollBackwardImpl_();
159     }
160     return false;
161 }
162 
SetActionAccessibilityFocusImpl(const ActionAccessibilityFocusImpl & actionAccessibilityFocusImpl)163 void AccessibilityNode::SetActionAccessibilityFocusImpl(
164     const ActionAccessibilityFocusImpl& actionAccessibilityFocusImpl)
165 {
166     actionAccessibilityFocusIdsImpl_ = actionAccessibilityFocusImpl;
167 }
168 
ActionAccessibilityFocus(bool isFocus)169 bool AccessibilityNode::ActionAccessibilityFocus(bool isFocus)
170 {
171     if (actionAccessibilityFocusIdsImpl_) {
172         actionAccessibilityFocusIdsImpl_(isFocus);
173         return true;
174     }
175     return false;
176 }
177 
SetActionFocusImpl(const ActionFocusImpl & actionFocusImpl)178 void AccessibilityNode::SetActionFocusImpl(const ActionFocusImpl& actionFocusImpl)
179 {
180     actionFocusImpl_ = actionFocusImpl;
181 }
182 
ActionFocus()183 bool AccessibilityNode::ActionFocus()
184 {
185     if (actionFocusImpl_) {
186         actionFocusImpl_();
187         return true;
188     }
189     return false;
190 }
191 
SetActionUpdateIdsImpl(const ActionUpdateIdsImpl & actionUpdateIdsImpl)192 void AccessibilityNode::SetActionUpdateIdsImpl(const ActionUpdateIdsImpl& actionUpdateIdsImpl)
193 {
194     actionUpdateIdsImpl_ = actionUpdateIdsImpl;
195 }
196 
ActionUpdateIds()197 void AccessibilityNode::ActionUpdateIds()
198 {
199     if (actionUpdateIdsImpl_) {
200         actionUpdateIdsImpl_();
201     }
202 }
203 
SetParentNode(const RefPtr<AccessibilityNode> & parentNode)204 void AccessibilityNode::SetParentNode(const RefPtr<AccessibilityNode>& parentNode)
205 {
206     parentNode_ = parentNode;
207 }
208 
SetPositionInfo(const PositionInfo & positionInfo)209 void AccessibilityNode::SetPositionInfo(const PositionInfo& positionInfo)
210 {
211     rect_.SetRect(positionInfo.left, positionInfo.top, positionInfo.width, positionInfo.height);
212 }
213 
SetAttr(const std::vector<std::pair<std::string,std::string>> & attrs)214 void AccessibilityNode::SetAttr(const std::vector<std::pair<std::string, std::string>>& attrs)
215 {
216     MergeItems(attrs, attrs_);
217 
218     for (const auto& attr : attrs) {
219         if (attr.first == ACCESSIBILITY_VALUE) {
220             text_ = attr.second;
221             if (tag_ == ACCESSIBILITY_TAG_TEXT && parentNode_.Upgrade() &&
222                 parentNode_.Upgrade()->GetTag() == ACCESSIBILITY_TAG_POPUP) {
223                 auto spParent = parentNode_.Upgrade();
224                 auto parentText = spParent->GetText() + text_;
225                 spParent->SetText(parentText);
226             }
227         } else if (attr.first == ACCESSIBILITY_DISABLED) {
228             isEnabled_ = !StringToBool(attr.second);
229         } else if (attr.first == ACCESSIBILITY_TYPE) {
230             inputType_ = attr.second;
231         } else if (attr.first == ACCESSIBILITY_GROUP) {
232             accessible_ = StringToBool(attr.second);
233         } else if (attr.first == ACCESSIBILITY_TEXT) {
234             accessibilityLabel_ = attr.second;
235         } else if (attr.first == ACCESSIBILITY_DESCRIPTION) {
236             accessibilityHint_ = attr.second;
237         } else if (attr.first == ACCESSIBILITY_IMPORTANCE) {
238             importantForAccessibility_ = attr.second;
239         } else if (attr.first == ID) {
240             jsComponentId_ = attr.second;
241         } else if (attr.first == ACCESSIBILITY_SHOW) {
242             shown_ = attr.second == "true";
243         }
244     }
245     SetOperableInfo();
246 }
247 
SetStyle(const std::vector<std::pair<std::string,std::string>> & styles)248 void AccessibilityNode::SetStyle(const std::vector<std::pair<std::string, std::string>>& styles)
249 {
250     MergeItems(styles, styles_);
251 }
252 
AddEvent(int32_t pageId,const std::vector<std::string> & events)253 void AccessibilityNode::AddEvent(int32_t pageId, const std::vector<std::string>& events)
254 {
255     for (const auto& event : events) {
256         if (event == ACCESSIBILITY_EVENT) {
257             onAccessibilityEventId_ = EventMarker(std::to_string(nodeId_), event, pageId);
258         } else if (event == CLICK) {
259             onClickId_ = EventMarker(std::to_string(nodeId_), event, pageId);
260             SetClickableState(true);
261         } else if (event == LONG_PRESS) {
262             onLongPressId_ = EventMarker(std::to_string(nodeId_), event, pageId);
263             SetLongClickableState(true);
264         } else if (event == FOCUS) {
265             onFocusId_ = EventMarker(std::to_string(nodeId_), event, pageId);
266         } else if (event == BLUR) {
267             onBlurId_ = EventMarker(std::to_string(nodeId_), event, pageId);
268         }
269     }
270     AddSupportAction(AceAction::CUSTOM_ACTION);
271     AddSupportAction(AceAction::GLOBAL_ACTION_BACK);
272 }
273 
AddNode(const RefPtr<AccessibilityNode> & node,int32_t slot)274 void AccessibilityNode::AddNode(const RefPtr<AccessibilityNode>& node, int32_t slot)
275 {
276     if (!node) {
277         LOGE("the node is nullptr");
278         return;
279     }
280     auto isExist = std::find_if(children_.begin(), children_.end(),
281         [node](const RefPtr<AccessibilityNode>& child) { return child->GetNodeId() == node->GetNodeId(); });
282     if (isExist != children_.end()) {
283         LOGD("the accessibility node[%{public}d] has already in the children", node->GetNodeId());
284         return;
285     }
286     auto pos = children_.begin();
287     std::advance(pos, slot);
288     children_.insert(pos, node);
289 }
290 
RemoveNode(const RefPtr<AccessibilityNode> & node)291 void AccessibilityNode::RemoveNode(const RefPtr<AccessibilityNode>& node)
292 {
293     if (!node) {
294         LOGE("the node is nullptr");
295         return;
296     }
297     children_.remove_if(
298         [node](const RefPtr<AccessibilityNode>& child) { return node->GetNodeId() == child->GetNodeId(); });
299 }
300 
Mount(int32_t slot)301 void AccessibilityNode::Mount(int32_t slot)
302 {
303     auto parentNode = parentNode_.Upgrade();
304     if (!parentNode) {
305         LOGE("the parent node is nullptr");
306         return;
307     }
308     parentNode->AddNode(AceType::Claim(this), slot);
309 }
310 
SetOperableInfo()311 void AccessibilityNode::SetOperableInfo()
312 {
313     static const LinearMapNode<OperableInfo> nodeOperatorMap[] = {
314         { ACCESSIBILITY_TAG_BUTTON,
315             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
316         { ACCESSIBILITY_TAG_CALENDAR,
317             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
318         { ACCESSIBILITY_TAG_CANVAS,
319             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
320         { ACCESSIBILITY_TAG_CHART,
321             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
322         { ACCESSIBILITY_TAG_CLOCK,
323             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
324         { ACCESSIBILITY_TAG_DIALOG,
325             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
326         { ACCESSIBILITY_TAG_DIV,
327             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
328         { ACCESSIBILITY_TAG_DIVIDER, { .checkable = false, .clickable = false, .scrollable = false,
329                                          .longClickable = false, .focusable = false } },
330         { ACCESSIBILITY_TAG_IMAGE,
331             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
332         { ACCESSIBILITY_TAG_INPUT,
333             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
334         { ACCESSIBILITY_TAG_LABEL,
335             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
336         { ACCESSIBILITY_TAG_LIST,
337             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
338         { ACCESSIBILITY_TAG_LIST_ITEM,
339             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
340         { ACCESSIBILITY_TAG_LIST_ITEM_GROUP,
341             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
342         { ACCESSIBILITY_TAG_MARQUEE,
343             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
344         { ACCESSIBILITY_TAG_NAVIGATION_BAR,
345             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
346         { ACCESSIBILITY_TAG_OPTION,
347             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
348         { ACCESSIBILITY_TAG_POPUP,
349             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
350         { ACCESSIBILITY_TAG_PROGRESS,
351             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
352         { ACCESSIBILITY_TAG_RATING,
353             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
354         { ACCESSIBILITY_TAG_REFRESH,
355             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
356         { ACCESSIBILITY_TAG_SELECT,
357             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
358         { ACCESSIBILITY_TAG_SLIDER,
359             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
360         { ACCESSIBILITY_TAG_SPAN,
361             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
362         { ACCESSIBILITY_TAG_STACK,
363             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
364         { ACCESSIBILITY_TAG_SWIPER,
365             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
366         { ACCESSIBILITY_TAG_SWITCH,
367             { .checkable = true, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
368         { ACCESSIBILITY_TAG_TAB_BAR,
369             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
370         { ACCESSIBILITY_TAG_TAB_CONTENT,
371             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
372         { ACCESSIBILITY_TAG_TABS,
373             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
374         { ACCESSIBILITY_TAG_TEXT,
375             { .checkable = false, .clickable = true, .scrollable = true, .longClickable = true, .focusable = true } },
376         { ACCESSIBILITY_TAG_TEXTAREA,
377             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
378         { ACCESSIBILITY_TAG_VIDEO,
379             { .checkable = false, .clickable = true, .scrollable = false, .longClickable = true, .focusable = true } },
380     };
381 
382     // set node operable info
383     int64_t operateIter = BinarySearchFindIndex(nodeOperatorMap, ArraySize(nodeOperatorMap), tag_.c_str());
384     if (operateIter != -1) {
385         isCheckable_ = nodeOperatorMap[operateIter].value.checkable;
386         isScrollable_ = nodeOperatorMap[operateIter].value.scrollable;
387         isFocusable_ = nodeOperatorMap[operateIter].value.focusable;
388         if (isFocusable_) {
389             AddSupportAction(AceAction::ACTION_FOCUS);
390         }
391     } else {
392         LOGW("node type %{public}s not support", tag_.c_str());
393         isCheckable_ = false;
394         isClickable_ = false;
395         isScrollable_ = false;
396         isLongClickable_ = false;
397         isFocusable_ = false;
398     }
399 
400     if (tag_ == ACCESSIBILITY_TAG_INPUT) {
401         if (inputType_ == INPUT_TYPE_CHECKBOX || inputType_ == INPUT_TYPE_RADIO) {
402             isCheckable_ = true;
403         } else if (inputType_ == INPUT_TYPE_PASSWORD) {
404             isPassword_ = true;
405         } else {
406             LOGW("node type %{public}s not support input event", tag_.c_str());
407         }
408     }
409 }
410 
GetSupportAction(uint64_t enableActions) const411 std::unordered_set<AceAction> AccessibilityNode::GetSupportAction(uint64_t enableActions) const
412 {
413     static const AceAction allActions[] = {
414         AceAction::ACTION_NONE, AceAction::GLOBAL_ACTION_BACK, AceAction::CUSTOM_ACTION, AceAction::ACTION_CLICK,
415         AceAction::ACTION_LONG_CLICK, AceAction::ACTION_SCROLL_FORWARD, AceAction::ACTION_SCROLL_BACKWARD,
416         AceAction::ACTION_FOCUS, AceAction::ACTION_ACCESSIBILITY_FOCUS, AceAction::ACTION_CLEAR_ACCESSIBILITY_FOCUS,
417         AceAction::ACTION_NEXT_AT_MOVEMENT_GRANULARITY, AceAction::ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
418     };
419 
420     std::unordered_set<AceAction> supportActions;
421     if (supportActions_ == 0) {
422         return supportActions;
423     }
424 
425     auto finalSupportActions = supportActions_ & enableActions;
426     for (auto action : allActions) {
427         if ((finalSupportActions & (1UL << static_cast<uint32_t>(action))) != 0) {
428             supportActions.emplace(action);
429         }
430     }
431     return supportActions;
432 }
433 
SetFocusChangeEventMarker(const EventMarker & eventId)434 void AccessibilityNode::SetFocusChangeEventMarker(const EventMarker& eventId)
435 {
436     if (eventId.IsEmpty()) {
437         return;
438     }
439 
440     auto container = Container::Current();
441     if (!container) {
442         LOGE("Container is null.");
443         return;
444     }
445     auto pipelineContext = container->GetPipelineContext();
446     if (!pipelineContext) {
447         LOGE("PipelineContext is null.");
448         return;
449     }
450     focusChangeEventId_ =
451         AceAsyncEvent<void(const std::string&)>::Create(eventId, pipelineContext);
452 }
453 
OnFocusChange(bool isFocus)454 void AccessibilityNode::OnFocusChange(bool isFocus)
455 {
456     if (focusChangeEventId_) {
457         auto json = JsonUtil::Create(true);
458         json->Put("eventType", isFocused_ ? "1" : "2");
459         focusChangeEventId_(json->ToString());
460     }
461 }
462 
463 } // namespace OHOS::Ace
464