5package org.qtproject.qt.android;
7import android.graphics.Rect;
8import android.os.Build;
9import android.os.Bundle;
10import android.system.Os;
11import android.text.SpannableString;
12import android.text.Spanned;
13import android.text.style.LocaleSpan;
14import android.text.TextUtils;
15import android.util.Log;
16import android.view.MotionEvent;
17import android.view.View;
18import android.view.ViewGroup;
19import android.view.ViewParent;
20import android.view.accessibility.AccessibilityEvent;
21import android.view.accessibility.AccessibilityManager;
22import android.view.accessibility.AccessibilityNodeInfo;
23import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
24import android.view.accessibility.AccessibilityNodeProvider;
25import java.util.Locale;
27class QtAccessibilityDelegate
extends View.AccessibilityDelegate
29 private static final String TAG =
"Qt A11Y";
33 static final int INVALID_ID = 333;
35 private View m_view =
null;
36 private AccessibilityManager m_manager;
37 private QtLayout m_layout;
41 private int m_focusedVirtualViewId = INVALID_ID;
43 private int m_hoveredVirtualViewId = INVALID_ID;
48 private final int[] m_globalOffset =
new int[2];
49 private int m_oldOffsetX = 0;
50 private int m_oldOffsetY = 0;
52 private class HoverEventListener
implements View.OnHoverListener
55 public boolean onHover(View
v, MotionEvent
event)
57 return dispatchHoverEvent(
event);
64 QtAccessibilityDelegate() { }
66 void initLayoutAccessibility(QtLayout layout)
69 Log.w(TAG,
"Unable to initialize the accessibility delegate with a null layout");
75 m_manager = m_layout.getContext().getSystemService(AccessibilityManager.class);
76 if (m_manager !=
null) {
77 AccessibilityManagerListener accServiceListener =
new AccessibilityManagerListener();
78 if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
79 Log.w(
"Qt A11y",
"Could not register a11y state change listener");
80 if (m_manager.isEnabled())
81 accServiceListener.onAccessibilityStateChanged(
true);
85 private class AccessibilityManagerListener
86 implements AccessibilityManager.AccessibilityStateChangeListener
89 public void onAccessibilityStateChanged(
boolean enabled)
94 final String isA11yOff = Os.getenv(
"QT_ANDROID_DISABLE_ACCESSIBILITY");
95 if (isA11yOff !=
null && (isA11yOff.equalsIgnoreCase(
"true") || isA11yOff.equals(
"1")))
100 m_layout.removeView(m_view);
103 QtNativeAccessibility.setActive(
enabled);
110 view =
new View(m_layout.getContext());
111 view.setId(View.NO_ID);
121 view.setAccessibilityDelegate(QtAccessibilityDelegate.this);
124 if (m_view ==
null) {
126 m_layout.addView(
view, m_layout.getChildCount(),
127 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
128 ViewGroup.LayoutParams.MATCH_PARENT));
132 m_view.setOnHoverListener(
new HoverEventListener());
133 }
catch (Exception e) {
135 Log.w(
"Qt A11y",
"Unknown exception: " + e);
138 QtNativeAccessibility.setActive(
enabled);
143 public AccessibilityNodeProvider getAccessibilityNodeProvider(View host)
145 return m_nodeProvider;
150 private boolean dispatchHoverEvent(MotionEvent
event)
152 if (m_manager ==
null || !m_manager.isTouchExplorationEnabled()) {
156 int virtualViewId = QtNativeAccessibility.hitTest(
event.getX(),
event.getY());
157 if (virtualViewId == INVALID_ID) {
158 virtualViewId = View.NO_ID;
161 switch (
event.getAction()) {
162 case MotionEvent.ACTION_HOVER_ENTER:
163 case MotionEvent.ACTION_HOVER_MOVE:
164 case MotionEvent.ACTION_HOVER_EXIT:
165 setHoveredVirtualViewId(virtualViewId);
172 SpannableString addLocaleSpan(
int viewId,
String value)
174 SpannableString localeValue =
new SpannableString(
value);
175 LocaleSpan localeSpan =
176 new LocaleSpan(Locale.forLanguageTag(QtNativeAccessibility.languageTag(viewId)));
177 localeValue.setSpan(localeSpan, 0, localeValue.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
181 void notifyScrolledEvent(
int viewId)
183 QtNative.runAction(() -> sendEventForVirtualViewId(viewId,
184 AccessibilityEvent.TYPE_VIEW_SCROLLED));
187 void notifyLocationChange(
int viewId)
189 QtNative.runAction(() -> {
190 if (m_focusedVirtualViewId == viewId)
191 invalidateVirtualViewId(m_focusedVirtualViewId);
195 void notifyObjectHide(
int viewId,
int parentId)
197 QtNative.runAction(() -> {
202 if (m_view !=
null && m_focusedVirtualViewId == viewId) {
203 m_focusedVirtualViewId = INVALID_ID;
205 sendEventForVirtualViewId(viewId,
206 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
210 invalidateVirtualViewId(parentId);
214 void notifyObjectShow(
int parentId)
216 QtNative.runAction(() -> {
219 invalidateVirtualViewId(parentId);
223 void notifyObjectFocus(
int viewId)
225 QtNative.runAction(() -> {
226 if (m_view ==
null || m_focusedVirtualViewId == viewId)
228 m_focusedVirtualViewId = viewId;
230 sendEventForVirtualViewId(viewId,
231 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
235 void notifyObjectDestroyed(
int viewId,
int parentId)
237 QtNative.runAction(() -> {
238 if (m_view !=
null && m_focusedVirtualViewId == viewId) {
239 m_focusedVirtualViewId = INVALID_ID;
241 sendEventForVirtualViewId(viewId,
242 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
244 invalidateVirtualViewId(parentId);
248 void notifyObjectCreated(
int parentId)
250 QtNative.runAction(() -> {
251 invalidateVirtualViewId(parentId);
255 void notifyValueChanged(
int viewId)
257 notifyContentChanged(viewId, AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
260 private void notifyContentChanged(
int viewId,
int contentChangeType)
262 if (m_manager ==
null)
265 QtNative.runAction(() -> {
269 if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
270 Log.w(TAG,
"notifyContentChanged() for invalid view");
274 final AccessibilityNodeInfo node = getNodeForVirtualViewId(viewId);
275 final AccessibilityEvent
event =
276 obtainAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
278 event.setEnabled(
true);
279 event.setClassName(node.getClassName());
280 event.setContentDescription(node.getContentDescription());
281 event.setContentChangeTypes(contentChangeType);
283 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
284 Log.w(TAG,
"No value to announce for " +
event.getClassName());
288 event.setPackageName(m_view.getContext().getPackageName());
289 event.setSource(m_view, viewId);
291 sendAccessibilityEvent(
event);
295 void notifyDescriptionOrNameChanged(
int viewId)
297 if (viewId == m_focusedVirtualViewId) {
298 notifyContentChanged(viewId,
299 AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
305 QtNative.runAction(() -> {
309 if (viewId == INVALID_ID) {
310 Log.w(TAG,
"notifyAnnouncementEvent() for invalid view");
314 if (!m_manager.isEnabled()) {
315 Log.w(TAG,
"notifyAnnouncementEvent for disabled AccessibilityManager");
319 final AccessibilityEvent
event =
320 obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
322 event.setClassName(getNodeForVirtualViewId(viewId).getClassName());
323 event.setPackageName(m_view.getContext().getPackageName());
324 sendAccessibilityEvent(
event);
328 void notifyTextChanged(
int viewId,
String text,
String beforeText,
329 int fromIndex,
int addedCount,
int removedCount)
331 QtNative.runAction(() -> {
332 if (m_view ==
null || m_manager ==
null || !m_manager.isEnabled())
338 (m_focusedVirtualViewId != INVALID_ID) ? m_focusedVirtualViewId : viewId;
339 if (targetId == INVALID_ID) {
340 Log.w(TAG,
"notifyTextChanged() with no focused view");
350 final CharSequence
className = getNodeForVirtualViewId(targetId).getClassName();
351 final String packageName = m_view.getContext().getPackageName();
353 final AccessibilityEvent
event =
354 obtainAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
355 event.setSource(m_view, targetId);
357 event.setPackageName(packageName);
358 event.getText().add(text);
359 event.setBeforeText(beforeText);
360 event.setFromIndex(fromIndex);
361 event.setAddedCount(addedCount);
362 event.setRemovedCount(removedCount);
363 sendAccessibilityEvent(
event);
367 final int caret = fromIndex + addedCount;
368 final AccessibilityEvent selEvent =
369 obtainAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
370 selEvent.setSource(m_view, targetId);
372 selEvent.setPackageName(packageName);
373 selEvent.getText().add(text);
374 selEvent.setFromIndex(caret);
375 selEvent.setToIndex(caret);
376 selEvent.setItemCount(text !=
null ?
text.length() : 0);
377 selEvent.setCurrentItemIndex(caret);
378 sendAccessibilityEvent(selEvent);
382 void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
384 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
eventType);
385 sendAccessibilityEvent(
event);
388 void sendAccessibilityEvent(AccessibilityEvent
event)
390 if (m_view ==
null ||
event ==
null)
393 final ViewGroup
group = (ViewGroup) m_view.getParent();
395 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. " +
396 "This should really not happen.");
400 group.requestSendAccessibilityEvent(m_view,
event);
403 void invalidateVirtualViewId(
int virtualViewId)
405 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
406 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
411 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
412 sendAccessibilityEvent(
event);
415 private void setHoveredVirtualViewId(
int virtualViewId)
417 if (m_hoveredVirtualViewId == virtualViewId) {
421 final int previousVirtualViewId = m_hoveredVirtualViewId;
422 m_hoveredVirtualViewId = virtualViewId;
423 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
424 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
427 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
429 final boolean isManagerEnabled = m_manager !=
null && m_manager.isEnabled();
430 if (m_view ==
null || !isManagerEnabled || (virtualViewId == INVALID_ID)) {
431 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
435 if (m_layout ==
null || m_layout.getChildCount() == 0)
438 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
440 event.setEnabled(
true);
441 event.setClassName(getNodeForVirtualViewId(virtualViewId).getClassName());
443 String description = QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId);
444 event.setContentDescription(addLocaleSpan(virtualViewId, description));
446 if (
eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED)
447 QtNativeAccessibility.populateScrollEvent(virtualViewId,
event);
449 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
450 Log.w(TAG,
"AccessibilityEvent with empty description");
452 event.setPackageName(m_view.getContext().getPackageName());
453 event.setSource(m_view, virtualViewId);
459 private void dumpNodes(
int parentId)
461 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
462 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
463 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
464 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
466 Log.i(TAG, parentId +
" has child: " +
id);
471 private AccessibilityNodeInfo getNodeForView()
473 if (m_view ==
null || m_layout ==
null)
474 return obtainAccessibilityNodeInfo();
478 final AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(m_view);
479 final AccessibilityNodeInfo
source = obtainAccessibilityNodeInfo(m_view);
480 m_view.onInitializeAccessibilityNodeInfo(
source);
483 m_view.getLocationOnScreen(m_globalOffset);
484 final int offsetX = m_globalOffset[0];
485 final int offsetY = m_globalOffset[1];
488 final Rect m_tempParentRect =
new Rect();
489 getBoundsInParent(
source, m_tempParentRect);
490 setBoundsInParent(
result, m_tempParentRect);
492 final Rect m_tempScreenRect =
new Rect();
493 source.getBoundsInScreen(m_tempScreenRect);
494 m_tempScreenRect.offset(offsetX, offsetY);
495 result.setBoundsInScreen(m_tempScreenRect);
498 final ViewParent parent = m_view.getParent();
499 if (parent instanceof View) {
500 result.setParent((View) parent);
510 if (m_layout.getChildCount() != 0) {
511 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
513 result.addChild(m_view,
id);
519 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
520 m_oldOffsetX = offsetX;
521 m_oldOffsetY = offsetY;
522 if (m_focusedVirtualViewId != INVALID_ID) {
523 m_nodeProvider.performAction(m_focusedVirtualViewId,
524 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
526 m_nodeProvider.performAction(m_focusedVirtualViewId,
527 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
535 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
537 if (m_view ==
null || m_layout ==
null)
538 return obtainAccessibilityNodeInfo();
540 final AccessibilityNodeInfo node = obtainAccessibilityNodeInfo();
542 node.setPackageName(m_view.getContext().getPackageName());
544 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
549 node.setSource(m_view, virtualViewId);
550 node.setContentDescription(addLocaleSpan(virtualViewId, node.getContentDescription().toString()));
552 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
553 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
555 int parentId = QtNativeAccessibility.parentId(virtualViewId);
556 node.setParent(m_view, parentId);
558 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
559 final int offsetX = m_globalOffset[0];
560 final int offsetY = m_globalOffset[1];
561 screenRect.offset(offsetX, offsetY);
562 node.setBoundsInScreen(screenRect);
564 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
565 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
566 setBoundsInParent(node, screenRect);
568 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_ON_SCREEN);
571 if (m_focusedVirtualViewId == virtualViewId) {
572 node.setAccessibilityFocused(
true);
573 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
575 node.setAccessibilityFocused(
false);
576 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
579 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
581 node.addChild(m_view,
id);
582 if (node.isScrollable()) {
583 setCollectionInfo(node,
ids.length, 1,
false);
589 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
592 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
594 if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
595 return getNodeForView();
597 return getNodeForVirtualViewId(virtualViewId);
601 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
603 if (m_view ==
null) {
604 Log.e(TAG,
"Unable to perform action with a null view");
608 boolean handled =
false;
610 if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_ON_SCREEN.getId()) {
611 handled = QtNativeAccessibility.showOnScreen(virtualViewId);
612 notifyScrolledEvent(virtualViewId);
615 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
616 if (m_focusedVirtualViewId == virtualViewId) {
617 m_focusedVirtualViewId = INVALID_ID;
624 sendEventForVirtualViewId(virtualViewId,
625 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
630 if (virtualViewId == View.NO_ID) {
631 return m_view.performAccessibilityAction(action,
arguments);
634 handled |= performActionForVirtualViewId(virtualViewId, action);
640 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
647 boolean success =
false;
649 case AccessibilityNodeInfo.ACTION_CLICK:
650 success = QtNativeAccessibility.clickAction(virtualViewId);
652 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
654 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
655 if (m_focusedVirtualViewId != virtualViewId) {
656 QtNativeAccessibility.focusAction(virtualViewId);
661 m_focusedVirtualViewId = INVALID_ID;
663 notifyObjectFocus(virtualViewId);
666 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
667 success = QtNativeAccessibility.scrollForward(virtualViewId);
669 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
670 success = QtNativeAccessibility.scrollBackward(virtualViewId);
672 case AccessibilityNodeInfo.ACTION_EXPAND:
673 success = QtNativeAccessibility.expand(virtualViewId);
675 case AccessibilityNodeInfo.ACTION_COLLAPSE:
676 success = QtNativeAccessibility.collapse(virtualViewId);
682 @SuppressWarnings(
"deprecation")
684 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
685 return new AccessibilityEvent(eventType);
687 return AccessibilityEvent.obtain(eventType);
691 @SuppressWarnings(
"deprecation")
692 private AccessibilityNodeInfo obtainAccessibilityNodeInfo() {
693 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
694 return new AccessibilityNodeInfo();
696 return AccessibilityNodeInfo.obtain();
700 @SuppressWarnings(
"deprecation")
701 private AccessibilityNodeInfo obtainAccessibilityNodeInfo(View
source) {
702 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
703 return new AccessibilityNodeInfo(source);
705 return AccessibilityNodeInfo.obtain(source);
709 @SuppressWarnings(
"deprecation")
710 private void getBoundsInParent(AccessibilityNodeInfo node, Rect outBounds) {
711 node.getBoundsInParent(outBounds);
714 @SuppressWarnings(
"deprecation")
715 private void setBoundsInParent(AccessibilityNodeInfo node, Rect bounds) {
716 node.setBoundsInParent(bounds);
719 @SuppressWarnings(
"deprecation")
720 private void setCollectionInfo(AccessibilityNodeInfo node,
int rowCount,
int columnCount,
721 boolean hierarchical) {
722 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
723 node.setCollectionInfo(new CollectionInfo(rowCount, columnCount, hierarchical));
725 node.setCollectionInfo(CollectionInfo.obtain(rowCount, columnCount, hierarchical));
QList< QVariant > arguments
QMap< Name, StatePointer > Bundle
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
GLenum GLenum GLsizei const GLuint * ids
[qttrid_noop]
GLuint GLsizei const GLchar * message
GLsizei const GLfloat * v
GLsizei GLsizei GLchar * source
static QEvent::Type eventType(int profilerEventType)