5package org.qtproject.qt.android;
7import android.app.Activity;
8import android.content.Context;
9import android.graphics.Rect;
10import android.os.Build;
11import android.os.Bundle;
12import android.system.Os;
13import android.text.TextUtils;
14import android.util.Log;
15import android.view.MotionEvent;
16import android.view.View;
17import android.view.ViewGroup;
18import android.view.ViewParent;
19import android.view.accessibility.AccessibilityEvent;
20import android.view.accessibility.AccessibilityManager;
21import android.view.accessibility.AccessibilityNodeInfo;
22import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
23import android.view.accessibility.AccessibilityNodeProvider;
25class QtAccessibilityDelegate
extends View.AccessibilityDelegate
27 private static final String TAG =
"Qt A11Y";
31 public static final int INVALID_ID = 333;
35 private static final String DEFAULT_CLASS_NAME =
"$VirtualChild";
37 private View m_view =
null;
38 private final AccessibilityManager m_manager;
39 private final QtLayout m_layout;
43 private int m_focusedVirtualViewId = INVALID_ID;
45 private int m_hoveredVirtualViewId = INVALID_ID;
50 private final int[] m_globalOffset =
new int[2];
51 private int m_oldOffsetX = 0;
52 private int m_oldOffsetY = 0;
54 private class HoverEventListener
implements View.OnHoverListener
57 public boolean onHover(
View v, MotionEvent
event)
59 return dispatchHoverEvent(
event);
64 public QtAccessibilityDelegate(QtLayout
layout)
68 m_manager = (AccessibilityManager) m_layout.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
69 if (m_manager !=
null) {
70 AccessibilityManagerListener accServiceListener =
new AccessibilityManagerListener();
71 if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
72 Log.w(
"Qt A11y",
"Could not register a11y state change listener");
73 if (m_manager.isEnabled())
74 accServiceListener.onAccessibilityStateChanged(
true);
78 private class AccessibilityManagerListener
implements AccessibilityManager.AccessibilityStateChangeListener
81 public void onAccessibilityStateChanged(
boolean enabled)
83 if (Os.getenv(
"QT_ANDROID_DISABLE_ACCESSIBILITY") !=
null)
89 view =
new View(m_layout.getContext());
100 view.setAccessibilityDelegate(QtAccessibilityDelegate.this);
103 if (m_view ==
null) {
105 m_layout.addView(
view, m_layout.getChildCount(),
106 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
110 m_view.setOnHoverListener(
new HoverEventListener());
111 }
catch (Exception e) {
113 Log.w(
"Qt A11y",
"Unknown exception: " + e);
116 if (m_view !=
null) {
117 m_layout.removeView(m_view);
122 QtNativeAccessibility.setActive(
enabled);
128 public AccessibilityNodeProvider getAccessibilityNodeProvider(
View host)
130 return m_nodeProvider;
135 private boolean dispatchHoverEvent(MotionEvent
event)
137 if (!m_manager.isTouchExplorationEnabled()) {
141 int virtualViewId = QtNativeAccessibility.hitTest(
event.getX(),
event.getY());
142 if (virtualViewId == INVALID_ID) {
143 virtualViewId =
View.NO_ID;
146 switch (
event.getAction()) {
147 case MotionEvent.ACTION_HOVER_ENTER:
148 case MotionEvent.ACTION_HOVER_MOVE:
149 case MotionEvent.ACTION_HOVER_EXIT:
150 setHoveredVirtualViewId(virtualViewId);
157 public void notifyScrolledEvent(
int viewId)
159 QtNative.runAction(() -> sendEventForVirtualViewId(viewId,
160 AccessibilityEvent.TYPE_VIEW_SCROLLED));
163 public void notifyLocationChange(
int viewId)
165 QtNative.runAction(() -> {
166 if (m_focusedVirtualViewId == viewId)
167 invalidateVirtualViewId(m_focusedVirtualViewId);
171 public void notifyObjectHide(
int viewId,
int parentId)
173 QtNative.runAction(() -> {
178 if (m_focusedVirtualViewId == viewId) {
179 m_focusedVirtualViewId = INVALID_ID;
181 sendEventForVirtualViewId(viewId,
182 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
186 invalidateVirtualViewId(parentId);
190 public void notifyObjectShow(
int parentId)
192 QtNative.runAction(() -> {
195 invalidateVirtualViewId(parentId);
199 public void notifyObjectFocus(
int viewId)
201 QtNative.runAction(() -> {
204 m_focusedVirtualViewId = viewId;
206 sendEventForVirtualViewId(viewId,
207 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
211 public void notifyValueChanged(
int viewId, String
value)
213 QtNative.runAction(() -> {
216 if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
217 Log.w(TAG,
"notifyValueChanged() for invalid view");
221 final ViewGroup
group = (ViewGroup) m_view.getParent();
223 Log.w(TAG,
"Could not announce value because ViewGroup was null.");
227 final AccessibilityEvent
event =
228 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
230 event.setEnabled(
true);
231 event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
233 event.setContentDescription(
value);
235 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
236 Log.w(TAG,
"No value to announce for " +
event.getClassName());
240 event.setPackageName(m_view.getContext().getPackageName());
241 event.setSource(m_view, viewId);
243 if (!
group.requestSendAccessibilityEvent(m_view,
event))
244 Log.w(TAG,
"Failed to send value change announcement for " +
event.getClassName());
248 public void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
250 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId, eventType);
251 sendAccessibilityEvent(
event);
254 public void sendAccessibilityEvent(AccessibilityEvent
event)
259 final ViewGroup
group = (ViewGroup) m_view.getParent();
261 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. This should really not happen.");
265 group.requestSendAccessibilityEvent(m_view,
event);
268 public void invalidateVirtualViewId(
int virtualViewId)
270 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
275 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
276 sendAccessibilityEvent(
event);
279 private void setHoveredVirtualViewId(
int virtualViewId)
281 if (m_hoveredVirtualViewId == virtualViewId) {
285 final int previousVirtualViewId = m_hoveredVirtualViewId;
286 m_hoveredVirtualViewId = virtualViewId;
287 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
288 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
291 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
293 if ((virtualViewId == INVALID_ID) || !m_manager.isEnabled()) {
294 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
298 if (m_layout.getChildCount() == 0)
301 final AccessibilityEvent
event = AccessibilityEvent.obtain(eventType);
303 event.setEnabled(
true);
304 event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
306 event.setContentDescription(QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId));
307 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
308 Log.w(TAG,
"AccessibilityEvent with empty description");
310 event.setPackageName(m_view.getContext().getPackageName());
311 event.setSource(m_view, virtualViewId);
317 private void dumpNodes(
int parentId)
319 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
320 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
321 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
322 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
324 Log.i(TAG, parentId +
" has child: " +
id);
329 private AccessibilityNodeInfo getNodeForView()
333 final AccessibilityNodeInfo
result = AccessibilityNodeInfo.obtain(m_view);
334 final AccessibilityNodeInfo
source = AccessibilityNodeInfo.obtain(m_view);
335 m_view.onInitializeAccessibilityNodeInfo(
source);
338 m_view.getLocationOnScreen(m_globalOffset);
339 final int offsetX = m_globalOffset[0];
340 final int offsetY = m_globalOffset[1];
343 final Rect m_tempParentRect =
new Rect();
344 source.getBoundsInParent(m_tempParentRect);
345 result.setBoundsInParent(m_tempParentRect);
347 final Rect m_tempScreenRect =
new Rect();
348 source.getBoundsInScreen(m_tempScreenRect);
349 m_tempScreenRect.offset(offsetX, offsetY);
350 result.setBoundsInScreen(m_tempScreenRect);
353 final ViewParent parent = m_view.getParent();
354 if (parent instanceof
View) {
365 if (m_layout.getChildCount() != 0) {
366 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
374 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
375 m_oldOffsetX = offsetX;
376 m_oldOffsetY = offsetY;
377 if (m_focusedVirtualViewId != INVALID_ID) {
378 m_nodeProvider.performAction(m_focusedVirtualViewId,
379 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
381 m_nodeProvider.performAction(m_focusedVirtualViewId,
382 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
390 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
392 final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
394 node.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
395 node.setPackageName(m_view.getContext().getPackageName());
397 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
402 node.setSource(m_view, virtualViewId);
404 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
405 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
407 int parentId = QtNativeAccessibility.parentId(virtualViewId);
408 node.setParent(m_view, parentId);
410 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
411 final int offsetX = m_globalOffset[0];
412 final int offsetY = m_globalOffset[1];
413 screenRect.offset(offsetX, offsetY);
414 node.setBoundsInScreen(screenRect);
416 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
417 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
418 node.setBoundsInParent(screenRect);
421 if (m_focusedVirtualViewId == virtualViewId) {
422 node.setAccessibilityFocused(
true);
423 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
425 node.setAccessibilityFocused(
false);
426 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
429 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
431 node.addChild(m_view,
id);
432 if (node.isScrollable()) {
433 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
434 node.setCollectionInfo(
new CollectionInfo(
ids.length, 1,
false));
436 node.setCollectionInfo(CollectionInfo.obtain(
ids.length, 1,
false));
443 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
446 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
448 if (virtualViewId ==
View.NO_ID || m_layout.getChildCount() == 0) {
449 return getNodeForView();
451 return getNodeForVirtualViewId(virtualViewId);
455 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
457 boolean handled =
false;
460 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
463 if (m_focusedVirtualViewId != virtualViewId) {
464 m_focusedVirtualViewId = virtualViewId;
466 sendEventForVirtualViewId(virtualViewId,
467 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
471 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
472 if (m_focusedVirtualViewId == virtualViewId) {
473 m_focusedVirtualViewId = INVALID_ID;
480 sendEventForVirtualViewId(virtualViewId,
481 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
486 if (virtualViewId ==
View.NO_ID) {
487 return m_view.performAccessibilityAction(action,
arguments);
490 handled |= performActionForVirtualViewId(virtualViewId, action);
496 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
503 boolean success =
false;
505 case AccessibilityNodeInfo.ACTION_CLICK:
506 success = QtNativeAccessibility.clickAction(virtualViewId);
508 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
510 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
511 success = QtNativeAccessibility.scrollForward(virtualViewId);
513 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_SCROLLED);
515 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
516 success = QtNativeAccessibility.scrollBackward(virtualViewId);
518 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_SCROLLED);
QList< QVariant > arguments
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
GLsizei const GLfloat * v
[13]
GLenum GLenum GLsizei const GLuint * ids
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLsizei GLsizei GLchar * source