67 QtAndroidPrivate::AndroidDeadlockProtector protector(
68 u"QtAndroidAccessibility::runInObjectContext()"_s);
69 if (!protector.acquire()) {
70 __android_log_print(ANDROID_LOG_WARN, m_qtTag,
71 "Could not run accessibility call in object context, accessing "
72 "main thread could lead to deadlock");
76 if (!QtAndroid::blockEventLoopsWhenSuspended()
77 || QGuiApplication::applicationState() != Qt::ApplicationSuspended) {
78 QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal);
80 __android_log_print(ANDROID_LOG_WARN, m_qtTag,
81 "Could not run accessibility call in object context, event loop suspended.");
297 QAccessibleInterface *iface = interfaceFromId(objectId);
298 if (!iface || !iface->isValid() || !iface->actionInterface())
301 const auto& actionNames = iface->actionInterface()->actionNames();
303 if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
304 invokeActionOnInterfaceInMainThread(iface->actionInterface(),
305 QAccessibleActionInterface::pressAction());
306 }
else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
307 invokeActionOnInterfaceInMainThread(iface->actionInterface(),
308 QAccessibleActionInterface::toggleAction());
406 QAccessibleValueInterface *valueIface = iface->valueInterface();
408 const QVariant valueVar = valueIface->currentValue();
409 const auto type = valueVar.typeId();
410 if (type == QMetaType::Double || type == QMetaType::Float) {
416 const double val = valueVar.toDouble();
418 bool stepIsValid =
false;
419 const double step = qAbs(valueIface->minimumStepSize().toDouble(&stepIsValid));
420 if (!stepIsValid || qFuzzyIsNull(step)) {
422 valueStr = qFuzzyIsNull(val) ? u"0"_s : QString::number(val,
'f');
424 const int precision = [](
double s) {
426 while (s < 1. && !qFuzzyCompare(s, 1.)) {
433 const int stop = count + 3;
434 const auto fractional = [](
double v) {
436 std::modf(v + 0.5, &whole);
437 return qAbs(v - whole);
440 while (count < stop && !qFuzzyIsNull(s)) {
442 s = fractional(s * 10);
446 valueStr = qFuzzyIsNull(val / step) ? u"0"_s
447 : QString::number(val,
'f', precision);
450 valueStr = valueVar.toString();
469 case QAccessible::Role::Button:
470 case QAccessible::Role::Link:
473 return QStringLiteral(
"android.widget.ToggleButton");
474 return QStringLiteral(
"android.widget.Button");
476 case QAccessible::Role::CheckBox:
479 return QStringLiteral(
"android.widget.CompoundButton");
480 case QAccessible::Role::Switch:
481 return QStringLiteral(
"android.widget.Switch");
482 case QAccessible::Role::Clock:
483 return QStringLiteral(
"android.widget.TextClock");
484 case QAccessible::Role::ComboBox:
485 return QStringLiteral(
"android.widget.Spinner");
486 case QAccessible::Role::Graphic:
488 return QStringLiteral(
"android.widget.ImageView");
489 case QAccessible::Role::Grouping:
490 return QStringLiteral(
"android.view.ViewGroup");
491 case QAccessible::Role::List:
494 return QStringLiteral(
"android.widget.AbsListView");
495 case QAccessible::Role::MenuItem:
496 return QStringLiteral(
"android.view.MenuItem");
497 case QAccessible::Role::PopupMenu:
498 return QStringLiteral(
"android.widget.PopupMenu");
499 case QAccessible::Role::Separator:
500 return QStringLiteral(
"android.widget.Space");
501 case QAccessible::Role::ToolBar:
502 return QStringLiteral(
"android.view.Toolbar");
503 case QAccessible::Role::Heading: [[fallthrough]];
504 case QAccessible::Role::StaticText:
506 return QStringLiteral(
"android.widget.TextView");
507 case QAccessible::Role::EditableText:
508 return QStringLiteral(
"android.widget.EditText");
509 case QAccessible::Role::RadioButton:
510 return QStringLiteral(
"android.widget.RadioButton");
511 case QAccessible::Role::ProgressBar:
512 return QStringLiteral(
"android.widget.ProgressBar");
514 case QAccessible::Role::SpinBox:
515 return QStringLiteral(
"android.widget.NumberPicker");
516 case QAccessible::Role::WebDocument:
517 return QStringLiteral(
"android.webkit.WebView");
518 case QAccessible::Role::Dialog:
519 return QStringLiteral(
"android.app.AlertDialog");
520 case QAccessible::Role::PageTab:
521 return QStringLiteral(
"android.app.ActionBar.Tab");
522 case QAccessible::Role::PageTabList:
523 return QStringLiteral(
"android.widget.TabWidget");
524 case QAccessible::Role::ScrollBar:
525 return QStringLiteral(
"android.widget.Scroller");
526 case QAccessible::Role::Slider:
527 return QStringLiteral(
"com.google.android.material.slider.Slider");
528 case QAccessible::Role::Table:
531 return QStringLiteral(
"android.widget.GridView");
532 case QAccessible::Role::Pane:
534 return QStringLiteral(
"android.view.ViewGroup");
535 case QAccessible::Role::AlertMessage:
536 case QAccessible::Role::Animation:
537 case QAccessible::Role::Application:
538 case QAccessible::Role::Assistant:
539 case QAccessible::Role::BlockQuote:
540 case QAccessible::Role::Border:
541 case QAccessible::Role::ButtonDropGrid:
542 case QAccessible::Role::ButtonDropDown:
543 case QAccessible::Role::ButtonMenu:
544 case QAccessible::Role::Canvas:
545 case QAccessible::Role::Caret:
546 case QAccessible::Role::Cell:
547 case QAccessible::Role::Chart:
548 case QAccessible::Role::Client:
549 case QAccessible::Role::ColorChooser:
550 case QAccessible::Role::Column:
551 case QAccessible::Role::ColumnHeader:
552 case QAccessible::Role::ComplementaryContent:
553 case QAccessible::Role::Cursor:
554 case QAccessible::Role::Desktop:
555 case QAccessible::Role::Dial:
556 case QAccessible::Role::Document:
557 case QAccessible::Role::Equation:
558 case QAccessible::Role::Footer:
559 case QAccessible::Role::Form:
560 case QAccessible::Role::Grip:
561 case QAccessible::Role::HelpBalloon:
562 case QAccessible::Role::HotkeyField:
563 case QAccessible::Role::Indicator:
564 case QAccessible::Role::LayeredPane:
565 case QAccessible::Role::ListItem:
566 case QAccessible::Role::MenuBar:
567 case QAccessible::Role::NoRole:
568 case QAccessible::Role::Note:
569 case QAccessible::Role::Notification:
570 case QAccessible::Role::Paragraph:
571 case QAccessible::Role::PropertyPage:
572 case QAccessible::Role::Row:
573 case QAccessible::Role::RowHeader:
574 case QAccessible::Role::Section:
575 case QAccessible::Role::Sound:
576 case QAccessible::Role::Splitter:
577 case QAccessible::Role::StatusBar:
578 case QAccessible::Role::Terminal:
579 case QAccessible::Role::TitleBar:
580 case QAccessible::Role::ToolTip:
581 case QAccessible::Role::Tree:
582 case QAccessible::Role::TreeItem:
583 case QAccessible::Role::UserRole:
584 case QAccessible::Role::Whitespace:
585 case QAccessible::Role::Window:
589 return QStringLiteral(
"android.view.TextView");
660 QAccessibleInterface *iface = interfaceFromId(objectId);
661 if (iface && iface->isValid()) {
663 info.state = iface->state();
664 info.role = iface->role();
665 info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface);
666 info.description = descriptionForInterface(iface);
667 info.identifier = QAccessibleBridgeUtils::accessibleId(iface);
668 QAccessibleTextInterface *textIface = iface->textInterface();
669 if (textIface && (textIface->selectionCount() > 0)) {
673 QAccessibleValueInterface *valueInterface = iface->valueInterface();
674 if (valueInterface) {
676 info.minValue = valueInterface->minimumValue();
677 info.maxValue = valueInterface->maximumValue();
678 info.currentValue = valueInterface->currentValue();
679 info.valueStepSize = valueInterface->minimumStepSize();
688 if (m_accessibilityContext) {
689 runInObjectContext(m_accessibilityContext, [objectId]() {
690 return populateNode_helper(objectId);
694 __android_log_print(ANDROID_LOG_WARN, m_qtTag,
"Accessibility: populateNode for Invalid ID");
698 const QString role = classNameForRole(info.role, info.state);
699 jstring jrole = env->NewString((jchar*)role.constData(), (jsize)role.size());
700 env->CallVoidMethod(node, m_setClassNameMethodID, jrole);
702 const bool hasClickableAction =
703 (info.actions.contains(QAccessibleActionInterface::pressAction())
704 || info.actions.contains(QAccessibleActionInterface::toggleAction()))
705 && !(info.role == QAccessible::StaticText || info.role == QAccessible::Heading);
706 const bool hasIncreaseAction =
707 info.actions.contains(QAccessibleActionInterface::increaseAction());
708 const bool hasDecreaseAction =
709 info.actions.contains(QAccessibleActionInterface::decreaseAction());
712 env->CallVoidMethod(node, m_setTextSelectionMethodID, info
.selectionStart,
716 if (info
.hasValue && m_setRangeInfoMethodID) {
717 int valueType = info.currentValue.typeId();
720 case QMetaType::Float:
721 case QMetaType::Double:
729 float min = info.minValue.toFloat();
730 float max = info.maxValue.toFloat();
731 float current = info.currentValue.toFloat();
732 if (info.role == QAccessible::ProgressBar) {
734 current = 100 * (current - min) / (max - min);
739 QJniObject rangeInfo(
"android/view/accessibility/AccessibilityNodeInfo$RangeInfo",
740 "(IFFF)V", rangeType, min, max, current);
742 if (rangeInfo.isValid()) {
743 env->CallVoidMethod(node, m_setRangeInfoMethodID, rangeInfo.object());
747 env->CallVoidMethod(node, m_setCheckableMethodID, (
bool)info.state.checkable);
748 env->CallVoidMethod(node, m_setCheckedMethodID, (
bool)info.state.checked);
749 env->CallVoidMethod(node, m_setEditableMethodID, info.state.editable);
750 env->CallVoidMethod(node, m_setEnabledMethodID, !info.state.disabled);
751 env->CallVoidMethod(node, m_setFocusableMethodID, (
bool)info.state.focusable);
752 env->CallVoidMethod(node, m_setFocusedMethodID, (
bool)info.state.focused);
753 if (m_setHeadingMethodID)
754 env->CallVoidMethod(node, m_setHeadingMethodID, info.role == QAccessible::Heading);
755 env->CallVoidMethod(node, m_setVisibleToUserMethodID, !info.state.invisible);
756 env->CallVoidMethod(node, m_setScrollableMethodID, hasIncreaseAction || hasDecreaseAction);
757 env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction || info.role == QAccessible::Link);
760 if (hasClickableAction)
761 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00000010);
764 if (hasIncreaseAction)
765 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00001000);
768 if (hasDecreaseAction)
769 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00002000);
772 jstring jdesc = env->NewString((jchar*)info.description.constData(),
773 (jsize)info.description.size());
775 env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc);
777 QJniObject(node).callMethod<
void>(
"setViewIdResourceName", info.identifier);
805 if (!env.registerNativeMethods(
"org/qtproject/qt/android/QtNativeAccessibility",
807 __android_log_print(ANDROID_LOG_FATAL,
"Qt A11y",
"RegisterNatives failed");
811 jclass nodeInfoClass = env->FindClass(
"android/view/accessibility/AccessibilityNodeInfo");
817 GET_AND_CHECK_STATIC_METHOD(m_setContentDescriptionMethodID, nodeInfoClass,
"setContentDescription",
"(Ljava/lang/CharSequence;)V");
822 if (QtAndroidPrivate::androidSdkVersion() >= 28) {
829 m_setRangeInfoMethodID, nodeInfoClass,
"setRangeInfo",
830 "(Landroid/view/accessibility/AccessibilityNodeInfo$RangeInfo;)V");