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 CharSequence
className = getNodeForVirtualViewId(viewId).getClassName();
247 ? AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
248 : AccessibilityEvent.TYPE_ANNOUNCEMENT;
249 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
251 event.setEnabled(
true);
253 event.setContentDescription(
value);
255 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
256 Log.w(TAG,
"No value to announce for " +
event.getClassName());
260 event.setPackageName(m_view.getContext().getPackageName());
261 event.setSource(m_view, viewId);
263 if (!
group.requestSendAccessibilityEvent(m_view,
event))
264 Log.w(TAG,
"Failed to send value change announcement for " +
event.getClassName());
268 void notifyDescriptionOrNameChanged(
int viewId,
String value)
270 if (viewId == m_focusedVirtualViewId)
271 notifyValueChanged(viewId,
value);
276 QtNative.runAction(() -> {
280 if (viewId == INVALID_ID) {
281 Log.w(TAG,
"notifyAnnouncementEvent() for invalid view");
285 if (!m_manager.isEnabled()) {
286 Log.w(TAG,
"notifyAnnouncementEvent for disabled AccessibilityManager");
290 final AccessibilityEvent
event =
291 obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
293 event.setClassName(getNodeForVirtualViewId(viewId).getClassName());
294 event.setPackageName(m_view.getContext().getPackageName());
295 sendAccessibilityEvent(
event);
299 void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
301 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
eventType);
302 sendAccessibilityEvent(
event);
305 void sendAccessibilityEvent(AccessibilityEvent
event)
307 if (m_view ==
null ||
event ==
null)
310 final ViewGroup
group = (ViewGroup) m_view.getParent();
312 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. " +
313 "This should really not happen.");
317 group.requestSendAccessibilityEvent(m_view,
event);
320 void invalidateVirtualViewId(
int virtualViewId)
322 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
323 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
328 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
329 sendAccessibilityEvent(
event);
332 private void setHoveredVirtualViewId(
int virtualViewId)
334 if (m_hoveredVirtualViewId == virtualViewId) {
338 final int previousVirtualViewId = m_hoveredVirtualViewId;
339 m_hoveredVirtualViewId = virtualViewId;
340 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
341 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
344 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
346 final boolean isManagerEnabled = m_manager !=
null && m_manager.isEnabled();
347 if (m_view ==
null || !isManagerEnabled || (virtualViewId == INVALID_ID)) {
348 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
352 if (m_layout ==
null || m_layout.getChildCount() == 0)
355 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
357 event.setEnabled(
true);
358 event.setClassName(getNodeForVirtualViewId(virtualViewId).getClassName());
360 event.setContentDescription(QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId));
361 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
362 Log.w(TAG,
"AccessibilityEvent with empty description");
364 event.setPackageName(m_view.getContext().getPackageName());
365 event.setSource(m_view, virtualViewId);
371 private void dumpNodes(
int parentId)
373 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
374 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
375 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
376 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
378 Log.i(TAG, parentId +
" has child: " +
id);
383 private AccessibilityNodeInfo getNodeForView()
385 if (m_view ==
null || m_layout ==
null)
386 return obtainAccessibilityNodeInfo();
390 final AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(m_view);
391 final AccessibilityNodeInfo
source = obtainAccessibilityNodeInfo(m_view);
392 m_view.onInitializeAccessibilityNodeInfo(
source);
395 m_view.getLocationOnScreen(m_globalOffset);
396 final int offsetX = m_globalOffset[0];
397 final int offsetY = m_globalOffset[1];
400 final Rect m_tempParentRect =
new Rect();
401 getBoundsInParent(
source, m_tempParentRect);
402 setBoundsInParent(
result, m_tempParentRect);
404 final Rect m_tempScreenRect =
new Rect();
405 source.getBoundsInScreen(m_tempScreenRect);
406 m_tempScreenRect.offset(offsetX, offsetY);
407 result.setBoundsInScreen(m_tempScreenRect);
410 final ViewParent parent = m_view.getParent();
411 if (parent instanceof View) {
412 result.setParent((View) parent);
422 if (m_layout.getChildCount() != 0) {
423 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
425 result.addChild(m_view,
id);
431 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
432 m_oldOffsetX = offsetX;
433 m_oldOffsetY = offsetY;
434 if (m_focusedVirtualViewId != INVALID_ID) {
435 m_nodeProvider.performAction(m_focusedVirtualViewId,
436 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
438 m_nodeProvider.performAction(m_focusedVirtualViewId,
439 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
447 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
449 if (m_view ==
null || m_layout ==
null)
450 return obtainAccessibilityNodeInfo();
452 final AccessibilityNodeInfo node = obtainAccessibilityNodeInfo();
454 node.setPackageName(m_view.getContext().getPackageName());
456 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
461 node.setSource(m_view, virtualViewId);
463 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
464 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
466 int parentId = QtNativeAccessibility.parentId(virtualViewId);
467 node.setParent(m_view, parentId);
469 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
470 final int offsetX = m_globalOffset[0];
471 final int offsetY = m_globalOffset[1];
472 screenRect.offset(offsetX, offsetY);
473 node.setBoundsInScreen(screenRect);
475 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
476 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
477 setBoundsInParent(node, screenRect);
480 if (m_focusedVirtualViewId == virtualViewId) {
481 node.setAccessibilityFocused(
true);
482 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
484 node.setAccessibilityFocused(
false);
485 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
488 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
490 node.addChild(m_view,
id);
491 if (node.isScrollable()) {
492 setCollectionInfo(node,
ids.length, 1,
false);
498 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
501 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
503 if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
504 return getNodeForView();
506 return getNodeForVirtualViewId(virtualViewId);
510 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
512 if (m_view ==
null) {
513 Log.e(TAG,
"Unable to perform action with a null view");
517 boolean handled =
false;
520 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
521 if (m_focusedVirtualViewId == virtualViewId) {
522 m_focusedVirtualViewId = INVALID_ID;
529 sendEventForVirtualViewId(virtualViewId,
530 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
535 if (virtualViewId == View.NO_ID) {
536 return m_view.performAccessibilityAction(action,
arguments);
539 handled |= performActionForVirtualViewId(virtualViewId, action);
545 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
552 boolean success =
false;
554 case AccessibilityNodeInfo.ACTION_CLICK:
555 success = QtNativeAccessibility.clickAction(virtualViewId);
557 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
559 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
560 if (m_focusedVirtualViewId != virtualViewId) {
561 success = QtNativeAccessibility.focusAction(virtualViewId);
563 notifyObjectFocus(virtualViewId);
568 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
569 success = QtNativeAccessibility.scrollForward(virtualViewId);
571 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
572 success = QtNativeAccessibility.scrollBackward(virtualViewId);
578 @SuppressWarnings(
"deprecation")
580 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
581 return new AccessibilityEvent(eventType);
583 return AccessibilityEvent.obtain(eventType);
587 @SuppressWarnings(
"deprecation")
588 private AccessibilityNodeInfo obtainAccessibilityNodeInfo() {
589 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
590 return new AccessibilityNodeInfo();
592 return AccessibilityNodeInfo.obtain();
596 @SuppressWarnings(
"deprecation")
597 private AccessibilityNodeInfo obtainAccessibilityNodeInfo(View
source) {
598 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
599 return new AccessibilityNodeInfo(source);
601 return AccessibilityNodeInfo.obtain(source);
605 @SuppressWarnings(
"deprecation")
606 private void getBoundsInParent(AccessibilityNodeInfo node, Rect outBounds) {
607 node.getBoundsInParent(outBounds);
610 @SuppressWarnings(
"deprecation")
611 private void setBoundsInParent(AccessibilityNodeInfo node, Rect bounds) {
612 node.setBoundsInParent(bounds);
615 @SuppressWarnings(
"deprecation")
616 private void setCollectionInfo(AccessibilityNodeInfo node,
int rowCount,
int columnCount,
617 boolean hierarchical) {
618 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
619 node.setCollectionInfo(new CollectionInfo(rowCount, columnCount, hierarchical));
621 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)