5package org.qtproject.qt.android;
7import android.graphics.Rect;
8import android.os.Build;
9import android.os.Bundle;
10import android.system.Os;
11import android.text.TextUtils;
12import android.util.Log;
13import android.view.MotionEvent;
14import android.view.View;
15import android.view.ViewGroup;
16import android.view.ViewParent;
17import android.view.accessibility.AccessibilityEvent;
18import android.view.accessibility.AccessibilityManager;
19import android.view.accessibility.AccessibilityNodeInfo;
20import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
21import android.view.accessibility.AccessibilityNodeProvider;
23class QtAccessibilityDelegate
extends View.AccessibilityDelegate
25 private static final String TAG =
"Qt A11Y";
29 static final int INVALID_ID = 333;
31 private View m_view =
null;
32 private AccessibilityManager m_manager;
33 private QtLayout m_layout;
37 private int m_focusedVirtualViewId = INVALID_ID;
39 private int m_hoveredVirtualViewId = INVALID_ID;
44 private final int[] m_globalOffset =
new int[2];
45 private int m_oldOffsetX = 0;
46 private int m_oldOffsetY = 0;
48 private class HoverEventListener
implements View.OnHoverListener
51 public boolean onHover(View
v, MotionEvent
event)
53 return dispatchHoverEvent(
event);
60 QtAccessibilityDelegate() { }
62 void initLayoutAccessibility(QtLayout
layout)
65 Log.w(TAG,
"Unable to initialize the accessibility delegate with a null layout");
71 m_manager = m_layout.getContext().getSystemService(AccessibilityManager.class);
72 if (m_manager !=
null) {
73 AccessibilityManagerListener accServiceListener =
new AccessibilityManagerListener();
74 if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
75 Log.w(
"Qt A11y",
"Could not register a11y state change listener");
76 if (m_manager.isEnabled())
77 accServiceListener.onAccessibilityStateChanged(
true);
81 private class AccessibilityManagerListener
82 implements AccessibilityManager.AccessibilityStateChangeListener
85 public void onAccessibilityStateChanged(
boolean enabled)
90 final String isA11yOff = Os.getenv(
"QT_ANDROID_DISABLE_ACCESSIBILITY");
91 if (isA11yOff !=
null && (isA11yOff.equalsIgnoreCase(
"true") || isA11yOff.equals(
"1")))
96 m_layout.removeView(m_view);
99 QtNativeAccessibility.setActive(
enabled);
106 view =
new View(m_layout.getContext());
107 view.setId(View.NO_ID);
117 view.setAccessibilityDelegate(QtAccessibilityDelegate.this);
120 if (m_view ==
null) {
122 m_layout.addView(
view, m_layout.getChildCount(),
123 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
124 ViewGroup.LayoutParams.MATCH_PARENT));
128 m_view.setOnHoverListener(
new HoverEventListener());
129 }
catch (Exception e) {
131 Log.w(
"Qt A11y",
"Unknown exception: " + e);
134 QtNativeAccessibility.setActive(
enabled);
139 public AccessibilityNodeProvider getAccessibilityNodeProvider(View host)
141 return m_nodeProvider;
146 private boolean dispatchHoverEvent(MotionEvent
event)
148 if (m_manager ==
null || !m_manager.isTouchExplorationEnabled()) {
152 int virtualViewId = QtNativeAccessibility.hitTest(
event.getX(),
event.getY());
153 if (virtualViewId == INVALID_ID) {
154 virtualViewId = View.NO_ID;
157 switch (
event.getAction()) {
158 case MotionEvent.ACTION_HOVER_ENTER:
159 case MotionEvent.ACTION_HOVER_MOVE:
160 case MotionEvent.ACTION_HOVER_EXIT:
161 setHoveredVirtualViewId(virtualViewId);
168 void notifyScrolledEvent(
int viewId)
170 QtNative.runAction(() -> sendEventForVirtualViewId(viewId,
171 AccessibilityEvent.TYPE_VIEW_SCROLLED));
174 void notifyLocationChange(
int viewId)
176 QtNative.runAction(() -> {
177 if (m_focusedVirtualViewId == viewId)
178 invalidateVirtualViewId(m_focusedVirtualViewId);
182 void notifyObjectHide(
int viewId,
int parentId)
184 QtNative.runAction(() -> {
189 if (m_view !=
null && m_focusedVirtualViewId == viewId) {
190 m_focusedVirtualViewId = INVALID_ID;
192 sendEventForVirtualViewId(viewId,
193 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
197 invalidateVirtualViewId(parentId);
201 void notifyObjectShow(
int parentId)
203 QtNative.runAction(() -> {
206 invalidateVirtualViewId(parentId);
210 void notifyObjectFocus(
int viewId)
212 QtNative.runAction(() -> {
215 m_focusedVirtualViewId = viewId;
217 sendEventForVirtualViewId(viewId,
218 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
224 if (m_manager ==
null)
227 QtNative.runAction(() -> {
233 if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
234 Log.w(TAG,
"notifyValueChanged() for invalid view");
238 final ViewGroup
group = (ViewGroup) m_view.getParent();
240 Log.w(TAG,
"Could not announce value because ViewGroup was null.");
244 final AccessibilityEvent
event =
245 obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
247 event.setEnabled(
true);
248 event.setClassName(getNodeForVirtualViewId(viewId).getClassName());
250 event.setContentDescription(
value);
252 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
253 Log.w(TAG,
"No value to announce for " +
event.getClassName());
257 event.setPackageName(m_view.getContext().getPackageName());
258 event.setSource(m_view, viewId);
260 if (!
group.requestSendAccessibilityEvent(m_view,
event))
261 Log.w(TAG,
"Failed to send value change announcement for " +
event.getClassName());
265 void notifyDescriptionOrNameChanged(
int viewId,
String value)
267 if (viewId == m_focusedVirtualViewId)
268 notifyValueChanged(viewId,
value);
273 QtNative.runAction(() -> {
277 if (viewId == INVALID_ID) {
278 Log.w(TAG,
"notifyAnnouncementEvent() for invalid view");
282 if (!m_manager.isEnabled()) {
283 Log.w(TAG,
"notifyAnnouncementEvent for disabled AccessibilityManager");
287 final AccessibilityEvent
event =
288 obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
290 event.setClassName(getNodeForVirtualViewId(viewId).getClassName());
291 event.setPackageName(m_view.getContext().getPackageName());
292 sendAccessibilityEvent(
event);
296 void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
298 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
eventType);
299 sendAccessibilityEvent(
event);
302 void sendAccessibilityEvent(AccessibilityEvent
event)
304 if (m_view ==
null ||
event ==
null)
307 final ViewGroup
group = (ViewGroup) m_view.getParent();
309 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. " +
310 "This should really not happen.");
314 group.requestSendAccessibilityEvent(m_view,
event);
317 void invalidateVirtualViewId(
int virtualViewId)
319 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
320 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
325 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
326 sendAccessibilityEvent(
event);
329 private void setHoveredVirtualViewId(
int virtualViewId)
331 if (m_hoveredVirtualViewId == virtualViewId) {
335 final int previousVirtualViewId = m_hoveredVirtualViewId;
336 m_hoveredVirtualViewId = virtualViewId;
337 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
338 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
341 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
343 final boolean isManagerEnabled = m_manager !=
null && m_manager.isEnabled();
344 if (m_view ==
null || !isManagerEnabled || (virtualViewId == INVALID_ID)) {
345 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
349 if (m_layout ==
null || m_layout.getChildCount() == 0)
352 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
354 event.setEnabled(
true);
355 event.setClassName(getNodeForVirtualViewId(virtualViewId).getClassName());
357 event.setContentDescription(QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId));
358 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
359 Log.w(TAG,
"AccessibilityEvent with empty description");
361 event.setPackageName(m_view.getContext().getPackageName());
362 event.setSource(m_view, virtualViewId);
368 private void dumpNodes(
int parentId)
370 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
371 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
372 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
373 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
375 Log.i(TAG, parentId +
" has child: " +
id);
380 private AccessibilityNodeInfo getNodeForView()
382 if (m_view ==
null || m_layout ==
null)
383 return obtainAccessibilityNodeInfo();
387 final AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(m_view);
388 final AccessibilityNodeInfo
source = obtainAccessibilityNodeInfo(m_view);
389 m_view.onInitializeAccessibilityNodeInfo(
source);
392 m_view.getLocationOnScreen(m_globalOffset);
393 final int offsetX = m_globalOffset[0];
394 final int offsetY = m_globalOffset[1];
397 final Rect m_tempParentRect =
new Rect();
398 getBoundsInParent(
source, m_tempParentRect);
399 setBoundsInParent(
result, m_tempParentRect);
401 final Rect m_tempScreenRect =
new Rect();
402 source.getBoundsInScreen(m_tempScreenRect);
403 m_tempScreenRect.offset(offsetX, offsetY);
404 result.setBoundsInScreen(m_tempScreenRect);
407 final ViewParent parent = m_view.getParent();
408 if (parent instanceof View) {
409 result.setParent((View) parent);
419 if (m_layout.getChildCount() != 0) {
420 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
422 result.addChild(m_view,
id);
428 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
429 m_oldOffsetX = offsetX;
430 m_oldOffsetY = offsetY;
431 if (m_focusedVirtualViewId != INVALID_ID) {
432 m_nodeProvider.performAction(m_focusedVirtualViewId,
433 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
435 m_nodeProvider.performAction(m_focusedVirtualViewId,
436 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
444 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
446 if (m_view ==
null || m_layout ==
null)
447 return obtainAccessibilityNodeInfo();
449 final AccessibilityNodeInfo node = obtainAccessibilityNodeInfo();
451 node.setPackageName(m_view.getContext().getPackageName());
453 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
458 node.setSource(m_view, virtualViewId);
460 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
461 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
463 int parentId = QtNativeAccessibility.parentId(virtualViewId);
464 node.setParent(m_view, parentId);
466 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
467 final int offsetX = m_globalOffset[0];
468 final int offsetY = m_globalOffset[1];
469 screenRect.offset(offsetX, offsetY);
470 node.setBoundsInScreen(screenRect);
472 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
473 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
474 setBoundsInParent(node, screenRect);
477 if (m_focusedVirtualViewId == virtualViewId) {
478 node.setAccessibilityFocused(
true);
479 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
481 node.setAccessibilityFocused(
false);
482 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
485 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
487 node.addChild(m_view,
id);
488 if (node.isScrollable()) {
489 setCollectionInfo(node,
ids.length, 1,
false);
495 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
498 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
500 if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
501 return getNodeForView();
503 return getNodeForVirtualViewId(virtualViewId);
507 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
509 if (m_view ==
null) {
510 Log.e(TAG,
"Unable to perform action with a null view");
514 boolean handled =
false;
517 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
518 if (m_focusedVirtualViewId == virtualViewId) {
519 m_focusedVirtualViewId = INVALID_ID;
526 sendEventForVirtualViewId(virtualViewId,
527 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
532 if (virtualViewId == View.NO_ID) {
533 return m_view.performAccessibilityAction(action,
arguments);
536 handled |= performActionForVirtualViewId(virtualViewId, action);
542 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
549 boolean success =
false;
551 case AccessibilityNodeInfo.ACTION_CLICK:
552 success = QtNativeAccessibility.clickAction(virtualViewId);
554 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
556 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
557 if (m_focusedVirtualViewId != virtualViewId) {
558 success = QtNativeAccessibility.focusAction(virtualViewId);
560 notifyObjectFocus(virtualViewId);
565 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
566 success = QtNativeAccessibility.scrollForward(virtualViewId);
568 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
569 success = QtNativeAccessibility.scrollBackward(virtualViewId);
575 @SuppressWarnings(
"deprecation")
577 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
578 return new AccessibilityEvent(eventType);
580 return AccessibilityEvent.obtain(eventType);
584 @SuppressWarnings(
"deprecation")
585 private AccessibilityNodeInfo obtainAccessibilityNodeInfo() {
586 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
587 return new AccessibilityNodeInfo();
589 return AccessibilityNodeInfo.obtain();
593 @SuppressWarnings(
"deprecation")
594 private AccessibilityNodeInfo obtainAccessibilityNodeInfo(View
source) {
595 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
596 return new AccessibilityNodeInfo(source);
598 return AccessibilityNodeInfo.obtain(source);
602 @SuppressWarnings(
"deprecation")
603 private void getBoundsInParent(AccessibilityNodeInfo node, Rect outBounds) {
604 node.getBoundsInParent(outBounds);
607 @SuppressWarnings(
"deprecation")
608 private void setBoundsInParent(AccessibilityNodeInfo node, Rect bounds) {
609 node.setBoundsInParent(bounds);
612 @SuppressWarnings(
"deprecation")
613 private void setCollectionInfo(AccessibilityNodeInfo node,
int rowCount,
int columnCount,
614 boolean hierarchical) {
615 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
616 node.setCollectionInfo(new CollectionInfo(rowCount, columnCount, hierarchical));
618 node.setCollectionInfo(CollectionInfo.obtain(rowCount, columnCount, hierarchical));
QList< QVariant > arguments
QMap< Name, StatePointer > Bundle
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
GLboolean GLuint group
[16]
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)
QGraphicsGridLayout * layout