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 notifyTextChanged(
int viewId,
String text,
String beforeText,
313 int fromIndex,
int addedCount,
int removedCount)
315 QtNative.runAction(() -> {
316 if (m_view ==
null || m_manager ==
null || !m_manager.isEnabled())
322 (m_focusedVirtualViewId != INVALID_ID) ? m_focusedVirtualViewId : viewId;
323 if (targetId == INVALID_ID) {
324 Log.w(TAG,
"notifyTextChanged() with no focused view");
334 final CharSequence
className = getNodeForVirtualViewId(targetId).getClassName();
335 final String packageName = m_view.getContext().getPackageName();
337 final AccessibilityEvent
event =
338 obtainAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
339 event.setSource(m_view, targetId);
341 event.setPackageName(packageName);
342 event.getText().add(text);
343 event.setBeforeText(beforeText);
344 event.setFromIndex(fromIndex);
345 event.setAddedCount(addedCount);
346 event.setRemovedCount(removedCount);
347 sendAccessibilityEvent(
event);
351 final int caret = fromIndex + addedCount;
352 final AccessibilityEvent selEvent =
353 obtainAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
354 selEvent.setSource(m_view, targetId);
356 selEvent.setPackageName(packageName);
357 selEvent.getText().add(text);
358 selEvent.setFromIndex(caret);
359 selEvent.setToIndex(caret);
360 selEvent.setItemCount(text !=
null ?
text.length() : 0);
361 selEvent.setCurrentItemIndex(caret);
362 sendAccessibilityEvent(selEvent);
366 void sendEventForVirtualViewId(
int virtualViewId,
int eventType)
368 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
eventType);
369 sendAccessibilityEvent(
event);
372 void sendAccessibilityEvent(AccessibilityEvent
event)
374 if (m_view ==
null ||
event ==
null)
377 final ViewGroup
group = (ViewGroup) m_view.getParent();
379 Log.w(TAG,
"Could not send AccessibilityEvent because group was null. " +
380 "This should really not happen.");
384 group.requestSendAccessibilityEvent(m_view,
event);
387 void invalidateVirtualViewId(
int virtualViewId)
389 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId,
390 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
395 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
396 sendAccessibilityEvent(
event);
399 private void setHoveredVirtualViewId(
int virtualViewId)
401 if (m_hoveredVirtualViewId == virtualViewId) {
405 final int previousVirtualViewId = m_hoveredVirtualViewId;
406 m_hoveredVirtualViewId = virtualViewId;
407 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
408 sendEventForVirtualViewId(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
411 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
413 final boolean isManagerEnabled = m_manager !=
null && m_manager.isEnabled();
414 if (m_view ==
null || !isManagerEnabled || (virtualViewId == INVALID_ID)) {
415 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
419 if (m_layout ==
null || m_layout.getChildCount() == 0)
422 final AccessibilityEvent
event = obtainAccessibilityEvent(
eventType);
424 event.setEnabled(
true);
425 event.setClassName(getNodeForVirtualViewId(virtualViewId).getClassName());
427 String description = QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId);
428 event.setContentDescription(addLocaleSpan(virtualViewId, description));
430 if (
eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED)
431 QtNativeAccessibility.populateScrollEvent(virtualViewId,
event);
433 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
434 Log.w(TAG,
"AccessibilityEvent with empty description");
436 event.setPackageName(m_view.getContext().getPackageName());
437 event.setSource(m_view, virtualViewId);
443 private void dumpNodes(
int parentId)
445 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
446 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
447 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
448 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
450 Log.i(TAG, parentId +
" has child: " +
id);
455 private AccessibilityNodeInfo getNodeForView()
457 if (m_view ==
null || m_layout ==
null)
458 return obtainAccessibilityNodeInfo();
462 final AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(m_view);
463 final AccessibilityNodeInfo
source = obtainAccessibilityNodeInfo(m_view);
464 m_view.onInitializeAccessibilityNodeInfo(
source);
467 m_view.getLocationOnScreen(m_globalOffset);
468 final int offsetX = m_globalOffset[0];
469 final int offsetY = m_globalOffset[1];
472 final Rect m_tempParentRect =
new Rect();
473 getBoundsInParent(
source, m_tempParentRect);
474 setBoundsInParent(
result, m_tempParentRect);
476 final Rect m_tempScreenRect =
new Rect();
477 source.getBoundsInScreen(m_tempScreenRect);
478 m_tempScreenRect.offset(offsetX, offsetY);
479 result.setBoundsInScreen(m_tempScreenRect);
482 final ViewParent parent = m_view.getParent();
483 if (parent instanceof View) {
484 result.setParent((View) parent);
494 if (m_layout.getChildCount() != 0) {
495 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
497 result.addChild(m_view,
id);
503 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
504 m_oldOffsetX = offsetX;
505 m_oldOffsetY = offsetY;
506 if (m_focusedVirtualViewId != INVALID_ID) {
507 m_nodeProvider.performAction(m_focusedVirtualViewId,
508 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
510 m_nodeProvider.performAction(m_focusedVirtualViewId,
511 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
519 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
521 if (m_view ==
null || m_layout ==
null)
522 return obtainAccessibilityNodeInfo();
524 final AccessibilityNodeInfo node = obtainAccessibilityNodeInfo();
526 node.setPackageName(m_view.getContext().getPackageName());
528 if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
533 node.setSource(m_view, virtualViewId);
534 node.setContentDescription(addLocaleSpan(virtualViewId, node.getContentDescription().toString()));
536 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
537 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
539 int parentId = QtNativeAccessibility.parentId(virtualViewId);
540 node.setParent(m_view, parentId);
542 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
543 final int offsetX = m_globalOffset[0];
544 final int offsetY = m_globalOffset[1];
545 screenRect.offset(offsetX, offsetY);
546 node.setBoundsInScreen(screenRect);
548 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
549 screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
550 setBoundsInParent(node, screenRect);
552 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_ON_SCREEN);
555 if (m_focusedVirtualViewId == virtualViewId) {
556 node.setAccessibilityFocused(
true);
557 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
559 node.setAccessibilityFocused(
false);
560 node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
563 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
565 node.addChild(m_view,
id);
566 if (node.isScrollable()) {
567 setCollectionInfo(node,
ids.length, 1,
false);
573 private final AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
576 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
578 if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
579 return getNodeForView();
581 return getNodeForVirtualViewId(virtualViewId);
585 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
587 if (m_view ==
null) {
588 Log.e(TAG,
"Unable to perform action with a null view");
592 boolean handled =
false;
594 if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_ON_SCREEN.getId()) {
595 handled = QtNativeAccessibility.showOnScreen(virtualViewId);
596 notifyScrolledEvent(virtualViewId);
599 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
600 if (m_focusedVirtualViewId == virtualViewId) {
601 m_focusedVirtualViewId = INVALID_ID;
608 sendEventForVirtualViewId(virtualViewId,
609 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
614 if (virtualViewId == View.NO_ID) {
615 return m_view.performAccessibilityAction(action,
arguments);
618 handled |= performActionForVirtualViewId(virtualViewId, action);
624 protected boolean performActionForVirtualViewId(
int virtualViewId,
int action)
631 boolean success =
false;
633 case AccessibilityNodeInfo.ACTION_CLICK:
634 success = QtNativeAccessibility.clickAction(virtualViewId);
636 sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
638 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
639 if (m_focusedVirtualViewId != virtualViewId) {
640 success = QtNativeAccessibility.focusAction(virtualViewId);
642 notifyObjectFocus(virtualViewId);
647 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
648 success = QtNativeAccessibility.scrollForward(virtualViewId);
650 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
651 success = QtNativeAccessibility.scrollBackward(virtualViewId);
653 case AccessibilityNodeInfo.ACTION_EXPAND:
654 success = QtNativeAccessibility.expand(virtualViewId);
656 case AccessibilityNodeInfo.ACTION_COLLAPSE:
657 success = QtNativeAccessibility.collapse(virtualViewId);
663 @SuppressWarnings(
"deprecation")
665 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
666 return new AccessibilityEvent(eventType);
668 return AccessibilityEvent.obtain(eventType);
672 @SuppressWarnings(
"deprecation")
673 private AccessibilityNodeInfo obtainAccessibilityNodeInfo() {
674 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
675 return new AccessibilityNodeInfo();
677 return AccessibilityNodeInfo.obtain();
681 @SuppressWarnings(
"deprecation")
682 private AccessibilityNodeInfo obtainAccessibilityNodeInfo(View
source) {
683 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
684 return new AccessibilityNodeInfo(source);
686 return AccessibilityNodeInfo.obtain(source);
690 @SuppressWarnings(
"deprecation")
691 private void getBoundsInParent(AccessibilityNodeInfo node, Rect outBounds) {
692 node.getBoundsInParent(outBounds);
695 @SuppressWarnings(
"deprecation")
696 private void setBoundsInParent(AccessibilityNodeInfo node, Rect bounds) {
697 node.setBoundsInParent(bounds);
700 @SuppressWarnings(
"deprecation")
701 private void setCollectionInfo(AccessibilityNodeInfo node,
int rowCount,
int columnCount,
702 boolean hierarchical) {
703 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
704 node.setCollectionInfo(new CollectionInfo(rowCount, columnCount, hierarchical));
706 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)