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