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:
474 return QStringLiteral(
"android.widget.ToggleButton");
475 return QStringLiteral(
"android.widget.Button");
477 case QAccessible::Role::CheckBox:
480 return QStringLiteral(
"android.widget.CompoundButton");
481 case QAccessible::Role::Clock:
482 return QStringLiteral(
"android.widget.TextClock");
483 case QAccessible::Role::ComboBox:
484 return QStringLiteral(
"android.widget.Spinner");
485 case QAccessible::Role::Graphic:
487 return QStringLiteral(
"android.widget.ImageView");
488 case QAccessible::Role::Grouping:
489 return QStringLiteral(
"android.view.ViewGroup");
490 case QAccessible::Role::List:
493 return QStringLiteral(
"android.widget.AbsListView");
494 case QAccessible::Role::MenuItem:
495 return QStringLiteral(
"android.view.MenuItem");
496 case QAccessible::Role::PopupMenu:
497 return QStringLiteral(
"android.widget.PopupMenu");
498 case QAccessible::Role::Separator:
499 return QStringLiteral(
"android.widget.Space");
500 case QAccessible::Role::ToolBar:
501 return QStringLiteral(
"android.view.Toolbar");
502 case QAccessible::Role::Heading: [[fallthrough]];
503 case QAccessible::Role::StaticText:
505 return QStringLiteral(
"android.widget.TextView");
506 case QAccessible::Role::EditableText:
507 return QStringLiteral(
"android.widget.EditText");
508 case QAccessible::Role::RadioButton:
509 return QStringLiteral(
"android.widget.RadioButton");
510 case QAccessible::Role::ProgressBar:
511 return QStringLiteral(
"android.widget.ProgressBar");
513 case QAccessible::Role::SpinBox:
514 return QStringLiteral(
"android.widget.NumberPicker");
515 case QAccessible::Role::WebDocument:
516 return QStringLiteral(
"android.webkit.WebView");
517 case QAccessible::Role::Dialog:
518 return QStringLiteral(
"android.app.AlertDialog");
519 case QAccessible::Role::PageTab:
520 return QStringLiteral(
"android.app.ActionBar.Tab");
521 case QAccessible::Role::PageTabList:
522 return QStringLiteral(
"android.widget.TabWidget");
523 case QAccessible::Role::ScrollBar:
524 return QStringLiteral(
"android.widget.Scroller");
525 case QAccessible::Role::Slider:
526 return QStringLiteral(
"com.google.android.material.slider.Slider");
527 case QAccessible::Role::Table:
530 return QStringLiteral(
"android.widget.GridView");
531 case QAccessible::Role::Pane:
533 return QStringLiteral(
"android.view.ViewGroup");
534 case QAccessible::Role::AlertMessage:
535 case QAccessible::Role::Animation:
536 case QAccessible::Role::Application:
537 case QAccessible::Role::Assistant:
538 case QAccessible::Role::BlockQuote:
539 case QAccessible::Role::Border:
540 case QAccessible::Role::ButtonDropGrid:
541 case QAccessible::Role::ButtonDropDown:
542 case QAccessible::Role::ButtonMenu:
543 case QAccessible::Role::Canvas:
544 case QAccessible::Role::Caret:
545 case QAccessible::Role::Cell:
546 case QAccessible::Role::Chart:
547 case QAccessible::Role::Client:
548 case QAccessible::Role::ColorChooser:
549 case QAccessible::Role::Column:
550 case QAccessible::Role::ColumnHeader:
551 case QAccessible::Role::ComplementaryContent:
552 case QAccessible::Role::Cursor:
553 case QAccessible::Role::Desktop:
554 case QAccessible::Role::Dial:
555 case QAccessible::Role::Document:
556 case QAccessible::Role::Equation:
557 case QAccessible::Role::Footer:
558 case QAccessible::Role::Form:
559 case QAccessible::Role::Grip:
560 case QAccessible::Role::HelpBalloon:
561 case QAccessible::Role::HotkeyField:
562 case QAccessible::Role::Indicator:
563 case QAccessible::Role::LayeredPane:
564 case QAccessible::Role::ListItem:
565 case QAccessible::Role::MenuBar:
566 case QAccessible::Role::NoRole:
567 case QAccessible::Role::Note:
568 case QAccessible::Role::Notification:
569 case QAccessible::Role::Paragraph:
570 case QAccessible::Role::PropertyPage:
571 case QAccessible::Role::Row:
572 case QAccessible::Role::RowHeader:
573 case QAccessible::Role::Section:
574 case QAccessible::Role::Sound:
575 case QAccessible::Role::Splitter:
576 case QAccessible::Role::StatusBar:
577 case QAccessible::Role::Terminal:
578 case QAccessible::Role::TitleBar:
579 case QAccessible::Role::ToolTip:
580 case QAccessible::Role::Tree:
581 case QAccessible::Role::TreeItem:
582 case QAccessible::Role::UserRole:
583 case QAccessible::Role::Whitespace:
584 case QAccessible::Role::Window:
588 return QStringLiteral(
"android.view.TextView");
659 QAccessibleInterface *iface = interfaceFromId(objectId);
660 if (iface && iface->isValid()) {
662 info.state = iface->state();
663 info.role = iface->role();
664 info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface);
665 info.description = descriptionForInterface(iface);
666 info.identifier = QAccessibleBridgeUtils::accessibleId(iface);
667 QAccessibleTextInterface *textIface = iface->textInterface();
668 if (textIface && (textIface->selectionCount() > 0)) {
672 QAccessibleValueInterface *valueInterface = iface->valueInterface();
673 if (valueInterface) {
675 info.minValue = valueInterface->minimumValue();
676 info.maxValue = valueInterface->maximumValue();
677 info.currentValue = valueInterface->currentValue();
678 info.valueStepSize = valueInterface->minimumStepSize();
687 if (m_accessibilityContext) {
688 runInObjectContext(m_accessibilityContext, [objectId]() {
689 return populateNode_helper(objectId);
693 __android_log_print(ANDROID_LOG_WARN, m_qtTag,
"Accessibility: populateNode for Invalid ID");
697 const QString role = classNameForRole(info.role, info.state);
698 jstring jrole = env->NewString((jchar*)role.constData(), (jsize)role.size());
699 env->CallVoidMethod(node, m_setClassNameMethodID, jrole);
701 const bool hasClickableAction =
702 (info.actions.contains(QAccessibleActionInterface::pressAction())
703 || info.actions.contains(QAccessibleActionInterface::toggleAction()))
704 && !(info.role == QAccessible::StaticText || info.role == QAccessible::Heading);
705 const bool hasIncreaseAction =
706 info.actions.contains(QAccessibleActionInterface::increaseAction());
707 const bool hasDecreaseAction =
708 info.actions.contains(QAccessibleActionInterface::decreaseAction());
711 env->CallVoidMethod(node, m_setTextSelectionMethodID, info
.selectionStart,
715 if (info
.hasValue && m_setRangeInfoMethodID) {
716 int valueType = info.currentValue.typeId();
719 case QMetaType::Float:
720 case QMetaType::Double:
728 QJniObject rangeInfo(
"android/view/accessibility/AccessibilityNodeInfo$RangeInfo",
729 "(IFFF)V", rangeType, info.minValue.toFloat(),
730 info.maxValue.toFloat(), info.currentValue.toFloat());
732 if (rangeInfo.isValid()) {
733 env->CallVoidMethod(node, m_setRangeInfoMethodID, rangeInfo.object());
737 env->CallVoidMethod(node, m_setCheckableMethodID, (
bool)info.state.checkable);
738 env->CallVoidMethod(node, m_setCheckedMethodID, (
bool)info.state.checked);
739 env->CallVoidMethod(node, m_setEditableMethodID, info.state.editable);
740 env->CallVoidMethod(node, m_setEnabledMethodID, !info.state.disabled);
741 env->CallVoidMethod(node, m_setFocusableMethodID, (
bool)info.state.focusable);
742 env->CallVoidMethod(node, m_setFocusedMethodID, (
bool)info.state.focused);
743 if (m_setHeadingMethodID)
744 env->CallVoidMethod(node, m_setHeadingMethodID, info.role == QAccessible::Heading);
745 env->CallVoidMethod(node, m_setVisibleToUserMethodID, !info.state.invisible);
746 env->CallVoidMethod(node, m_setScrollableMethodID, hasIncreaseAction || hasDecreaseAction);
747 env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction || info.role == QAccessible::Link);
750 if (hasClickableAction)
751 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00000010);
754 if (hasIncreaseAction)
755 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00001000);
758 if (hasDecreaseAction)
759 env->CallVoidMethod(node, m_addActionMethodID, (
int)0x00002000);
762 jstring jdesc = env->NewString((jchar*)info.description.constData(),
763 (jsize)info.description.size());
765 env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc);
767 QJniObject(node).callMethod<
void>(
"setViewIdResourceName", info.identifier);
795 if (!env.registerNativeMethods(
"org/qtproject/qt/android/QtNativeAccessibility",
797 __android_log_print(ANDROID_LOG_FATAL,
"Qt A11y",
"RegisterNatives failed");
801 jclass nodeInfoClass = env->FindClass(
"android/view/accessibility/AccessibilityNodeInfo");
807 GET_AND_CHECK_STATIC_METHOD(m_setContentDescriptionMethodID, nodeInfoClass,
"setContentDescription",
"(Ljava/lang/CharSequence;)V");
812 if (QtAndroidPrivate::androidSdkVersion() >= 28) {
819 m_setRangeInfoMethodID, nodeInfoClass,
"setRangeInfo",
820 "(Landroid/view/accessibility/AccessibilityNodeInfo$RangeInfo;)V");