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(() -> {
228 m_focusedVirtualViewId = viewId;
230 sendEventForVirtualViewId(viewId,
231 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
237 if (m_manager ==
null)
240 QtNative.runAction(() -> {
246 if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
247 Log.w(TAG,
"notifyValueChanged() for invalid view");
251 final ViewGroup
group = (ViewGroup) m_view.getParent();
253 Log.w(TAG,
"Could not announce value because ViewGroup was null.");
257 final CharSequence
className = getNodeForVirtualViewId(viewId).getClassName();
260 ? AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
261 : AccessibilityEvent.TYPE_ANNOUNCEMENT;
262 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
264 event.setEnabled(
true);
266 event.setContentDescription(addLocaleSpan(viewId,
value));
268 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
269 Log.w(TAG,
"No value to announce for " +
event.getClassName());
273 event.setPackageName(m_view.getContext().getPackageName());
274 event.setSource(m_view, viewId);
276 if (!
group.requestSendAccessibilityEvent(m_view,
event))
277 Log.w(TAG,
"Failed to send value change announcement for " +
event.getClassName());
281 void notifyDescriptionOrNameChanged(
int viewId,
String value)
283 if (viewId == m_focusedVirtualViewId)
284 notifyValueChanged(viewId,
value);
289 QtNative.runAction(() -> {
293 if (viewId == INVALID_ID) {
294 Log.w(TAG,
"notifyAnnouncementEvent() for invalid view");
298 if (!m_manager.isEnabled()) {
299 Log.w(TAG,
"notifyAnnouncementEvent for disabled AccessibilityManager");
303 final AccessibilityEvent
event =
304 obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
306 event.setClassName(getNodeForVirtualViewId(viewId).getClassName());
307 event.setPackageName(m_view.getContext().getPackageName());
308 sendAccessibilityEvent(
event);
312 void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
314 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
eventType);
315 sendAccessibilityEvent(
event);
318 void sendAccessibilityEvent(AccessibilityEvent
event)
320 if (m_view ==
null ||
event ==
null)
323 final ViewGroup
group = (ViewGroup) m_view.getParent();
325 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. " +
326 "This should really not happen.");
330 group.requestSendAccessibilityEvent(m_view,
event);
333 void invalidateVirtualViewId(
int virtualViewId)
335 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
336 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
341 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
342 sendAccessibilityEvent(
event);
345 private void setHoveredVirtualViewId(
int virtualViewId)
347 if (m_hoveredVirtualViewId == virtualViewId) {
351 final int previousVirtualViewId = m_hoveredVirtualViewId;
352 m_hoveredVirtualViewId = virtualViewId;
353 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
354 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
357 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
359 final boolean isManagerEnabled = m_manager !=
null && m_manager.isEnabled();
360 if (m_view ==
null || !isManagerEnabled || (virtualViewId == INVALID_ID)) {
361 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
365 if (m_layout ==
null || m_layout.getChildCount() == 0)
368 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
370 event.setEnabled(
true);
371 event.setClassName(getNodeForVirtualViewId(virtualViewId).getClassName());
373 String description = QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId);
374 event.setContentDescription(addLocaleSpan(virtualViewId, description));
376 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
377 Log.w(TAG,
"AccessibilityEvent with empty description");
379 event.setPackageName(m_view.getContext().getPackageName());
380 event.setSource(m_view, virtualViewId);
386 private void dumpNodes(
int parentId)
388 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
389 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
390 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
391 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
393 Log.i(TAG, parentId +
" has child: " +
id);
398 private AccessibilityNodeInfo getNodeForView()
400 if (m_view ==
null || m_layout ==
null)
401 return obtainAccessibilityNodeInfo();
405 final AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(m_view);
406 final AccessibilityNodeInfo
source = obtainAccessibilityNodeInfo(m_view);
407 m_view.onInitializeAccessibilityNodeInfo(
source);
410 m_view.getLocationOnScreen(m_globalOffset);
411 final int offsetX = m_globalOffset[0];
412 final int offsetY = m_globalOffset[1];
415 final Rect m_tempParentRect =
new Rect();
416 getBoundsInParent(
source, m_tempParentRect);
417 setBoundsInParent(
result, m_tempParentRect);
419 final Rect m_tempScreenRect =
new Rect();
420 source.getBoundsInScreen(m_tempScreenRect);
421 m_tempScreenRect.offset(offsetX, offsetY);
422 result.setBoundsInScreen(m_tempScreenRect);
425 final ViewParent parent = m_view.getParent();
426 if (parent instanceof View) {
427 result.setParent((View) parent);
437 if (m_layout.getChildCount() != 0) {
438 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
440 result.addChild(m_view,
id);
446 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
447 m_oldOffsetX = offsetX;
448 m_oldOffsetY = offsetY;
449 if (m_focusedVirtualViewId != INVALID_ID) {
450 m_nodeProvider.performAction(m_focusedVirtualViewId,
451 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
453 m_nodeProvider.performAction(m_focusedVirtualViewId,
454 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
462 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
464 if (m_view ==
null || m_layout ==
null)
465 return obtainAccessibilityNodeInfo();
467 final AccessibilityNodeInfo node = obtainAccessibilityNodeInfo();
469 node.setPackageName(m_view.getContext().getPackageName());
471 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
476 node.setSource(m_view, virtualViewId);
477 node.setContentDescription(addLocaleSpan(virtualViewId, node.getContentDescription().toString()));
479 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
480 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
482 int parentId = QtNativeAccessibility.parentId(virtualViewId);
483 node.setParent(m_view, parentId);
485 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
486 final int offsetX = m_globalOffset[0];
487 final int offsetY = m_globalOffset[1];
488 screenRect.offset(offsetX, offsetY);
489 node.setBoundsInScreen(screenRect);
491 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
492 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
493 setBoundsInParent(node, screenRect);
496 if (m_focusedVirtualViewId == virtualViewId) {
497 node.setAccessibilityFocused(
true);
498 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
500 node.setAccessibilityFocused(
false);
501 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
504 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
506 node.addChild(m_view,
id);
507 if (node.isScrollable()) {
508 setCollectionInfo(node,
ids.length, 1,
false);
514 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
517 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
519 if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
520 return getNodeForView();
522 return getNodeForVirtualViewId(virtualViewId);
526 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
528 if (m_view ==
null) {
529 Log.e(TAG,
"Unable to perform action with a null view");
533 boolean handled =
false;
536 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
537 if (m_focusedVirtualViewId == virtualViewId) {
538 m_focusedVirtualViewId = INVALID_ID;
545 sendEventForVirtualViewId(virtualViewId,
546 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
551 if (virtualViewId == View.NO_ID) {
552 return m_view.performAccessibilityAction(action,
arguments);
555 handled |= performActionForVirtualViewId(virtualViewId, action);
561 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
568 boolean success =
false;
570 case AccessibilityNodeInfo.ACTION_CLICK:
571 success = QtNativeAccessibility.clickAction(virtualViewId);
573 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
575 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
576 if (m_focusedVirtualViewId != virtualViewId) {
577 success = QtNativeAccessibility.focusAction(virtualViewId);
579 notifyObjectFocus(virtualViewId);
584 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
585 success = QtNativeAccessibility.scrollForward(virtualViewId);
587 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
588 success = QtNativeAccessibility.scrollBackward(virtualViewId);
594 @SuppressWarnings(
"deprecation")
596 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
597 return new AccessibilityEvent(eventType);
599 return AccessibilityEvent.obtain(eventType);
603 @SuppressWarnings(
"deprecation")
604 private AccessibilityNodeInfo obtainAccessibilityNodeInfo() {
605 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
606 return new AccessibilityNodeInfo();
608 return AccessibilityNodeInfo.obtain();
612 @SuppressWarnings(
"deprecation")
613 private AccessibilityNodeInfo obtainAccessibilityNodeInfo(View
source) {
614 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
615 return new AccessibilityNodeInfo(source);
617 return AccessibilityNodeInfo.obtain(source);
621 @SuppressWarnings(
"deprecation")
622 private void getBoundsInParent(AccessibilityNodeInfo node, Rect outBounds) {
623 node.getBoundsInParent(outBounds);
626 @SuppressWarnings(
"deprecation")
627 private void setBoundsInParent(AccessibilityNodeInfo node, Rect bounds) {
628 node.setBoundsInParent(bounds);
631 @SuppressWarnings(
"deprecation")
632 private void setCollectionInfo(AccessibilityNodeInfo node,
int rowCount,
int columnCount,
633 boolean hierarchical) {
634 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
635 node.setCollectionInfo(new CollectionInfo(rowCount, columnCount, hierarchical));
637 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)