5package org.qtproject.qt.android.accessibility;
7import android.accessibilityservice.AccessibilityService;
8import android.app.Activity;
9import android.graphics.Rect;
10import android.os.Build;
11import android.os.Bundle;
12import android.util.Log;
13import android.view.View;
14import android.view.ViewGroup;
15import android.view.ViewParent;
16import android.text.TextUtils;
18import android.view.accessibility.*;
19import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
20import android.view.MotionEvent;
21import android.view.View.OnHoverListener;
23import android.content.Context;
24import android.system.Os;
26import java.util.LinkedList;
33 private static final String TAG =
"Qt A11Y";
41 private static final String DEFAULT_CLASS_NAME =
"$VirtualChild";
43 private View m_view =
null;
44 private AccessibilityManager m_manager;
46 private Activity m_activity;
47 private ViewGroup m_layout;
51 private int m_focusedVirtualViewId =
INVALID_ID;
53 private int m_hoveredVirtualViewId =
INVALID_ID;
58 private final int[] m_globalOffset =
new int[2];
59 private int m_oldOffsetX = 0;
60 private int m_oldOffsetY = 0;
62 private class HoverEventListener
implements View.OnHoverListener
65 public boolean onHover(View
v, MotionEvent
event)
67 return dispatchHoverEvent(
event);
73 m_activity = activity;
75 m_activityDelegate = activityDelegate;
77 m_manager = (AccessibilityManager) m_activity.getSystemService(Context.ACCESSIBILITY_SERVICE);
78 if (m_manager !=
null) {
79 AccessibilityManagerListener accServiceListener =
new AccessibilityManagerListener();
80 if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
81 Log.w(
"Qt A11y",
"Could not register a11y state change listener");
82 if (m_manager.isEnabled())
83 accServiceListener.onAccessibilityStateChanged(
true);
87 private class AccessibilityManagerListener
implements AccessibilityManager.AccessibilityStateChangeListener
90 public void onAccessibilityStateChanged(
boolean enabled)
92 if (Os.getenv(
"QT_ANDROID_DISABLE_ACCESSIBILITY") !=
null)
98 view =
new View(m_activity);
99 view.setId(View.NO_ID);
109 if (m_view ==
null) {
112 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
116 m_view.setOnHoverListener(
new HoverEventListener());
117 }
catch (Exception
e) {
119 Log.w(
"Qt A11y",
"Unknown exception: " +
e.toString());
122 if (m_view !=
null) {
123 m_layout.removeView(m_view);
128 QtNativeAccessibility.setActive(
enabled);
136 return m_nodeProvider;
141 private boolean dispatchHoverEvent(MotionEvent
event)
143 if (!m_manager.isTouchExplorationEnabled()) {
147 int virtualViewId = QtNativeAccessibility.hitTest(
event.getX(),
event.getY());
149 virtualViewId = View.NO_ID;
152 switch (
event.getAction()) {
153 case MotionEvent.ACTION_HOVER_ENTER:
154 case MotionEvent.ACTION_HOVER_MOVE:
155 setHoveredVirtualViewId(virtualViewId);
157 case MotionEvent.ACTION_HOVER_EXIT:
158 setHoveredVirtualViewId(virtualViewId);
172 if (m_focusedVirtualViewId == viewId)
182 if (m_focusedVirtualViewId == viewId) {
186 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
197 m_focusedVirtualViewId = viewId;
200 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
207 if ((viewId ==
INVALID_ID) || !m_manager.isEnabled()) {
208 Log.w(
TAG,
"notifyValueChanged() for invalid view");
212 final ViewGroup
group = (ViewGroup)m_view.getParent();
214 Log.w(
TAG,
"Could not announce value because ViewGroup was null.");
218 final AccessibilityEvent
event =
219 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
221 event.setEnabled(
true);
222 event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
224 event.setContentDescription(
value);
226 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription())) {
227 Log.w(
TAG,
"No value to announce for " +
event.getClassName());
231 event.setPackageName(m_view.getContext().getPackageName());
232 event.setSource(m_view, viewId);
234 if (!
group.requestSendAccessibilityEvent(m_view,
event))
235 Log.w(
TAG,
"Failed to send value change announcement for " +
event.getClassName());
240 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId, eventType);
249 final ViewGroup
group = (ViewGroup) m_view.getParent();
251 Log.w(
TAG,
"Could not send AccessibilityEvent because group was null. This should really not happen.");
255 return group.requestSendAccessibilityEvent(m_view,
event);
260 final AccessibilityEvent
event = getEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
265 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
269 private void setHoveredVirtualViewId(
int virtualViewId)
271 if (m_hoveredVirtualViewId == virtualViewId) {
275 final int previousVirtualViewId = m_hoveredVirtualViewId;
276 m_hoveredVirtualViewId = virtualViewId;
281 private AccessibilityEvent getEventForVirtualViewId(
int virtualViewId,
int eventType)
283 if ((virtualViewId ==
INVALID_ID) || !m_manager.isEnabled()) {
284 Log.w(TAG,
"getEventForVirtualViewId for invalid view");
291 final AccessibilityEvent
event = AccessibilityEvent.obtain(eventType);
293 event.setEnabled(
true);
294 event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
296 event.setContentDescription(QtNativeAccessibility.descriptionForAccessibleObject(virtualViewId));
297 if (
event.getText().isEmpty() && TextUtils.isEmpty(
event.getContentDescription()))
298 Log.w(TAG,
"AccessibilityEvent with empty description");
300 event.setPackageName(m_view.getContext().getPackageName());
301 event.setSource(m_view, virtualViewId);
305 private void dumpNodes(
int parentId)
307 Log.i(TAG,
"A11Y hierarchy: " + parentId +
" parent: " + QtNativeAccessibility.parentId(parentId));
308 Log.i(TAG,
" desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) +
" rect: " + QtNativeAccessibility.screenRect(parentId));
309 Log.i(TAG,
" NODE: " + getNodeForVirtualViewId(parentId));
310 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
311 for (
int i = 0;
i <
ids.length; ++
i) {
312 Log.i(TAG, parentId +
" has child: " +
ids[
i]);
317 private AccessibilityNodeInfo getNodeForView()
321 final AccessibilityNodeInfo
result = AccessibilityNodeInfo.obtain(m_view);
322 final AccessibilityNodeInfo
source = AccessibilityNodeInfo.obtain(m_view);
323 m_view.onInitializeAccessibilityNodeInfo(
source);
326 m_view.getLocationOnScreen(m_globalOffset);
327 final int offsetX = m_globalOffset[0];
328 final int offsetY = m_globalOffset[1];
331 final Rect m_tempParentRect =
new Rect();
332 source.getBoundsInParent(m_tempParentRect);
333 result.setBoundsInParent(m_tempParentRect);
335 final Rect m_tempScreenRect =
new Rect();
336 source.getBoundsInScreen(m_tempScreenRect);
337 m_tempScreenRect.offset(offsetX, offsetY);
338 result.setBoundsInScreen(m_tempScreenRect);
341 final ViewParent
parent = m_view.getParent();
354 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
355 for (
int i = 0;
i <
ids.length; ++
i)
362 if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
363 m_oldOffsetX = offsetX;
364 m_oldOffsetY = offsetY;
366 m_nodeProvider.performAction(m_focusedVirtualViewId,
367 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
369 m_nodeProvider.performAction(m_focusedVirtualViewId,
370 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
378 private AccessibilityNodeInfo getNodeForVirtualViewId(
int virtualViewId)
380 final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
382 node.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
383 node.setPackageName(m_view.getContext().getPackageName());
385 if (m_activityDelegate.
getSurfaceCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
390 node.setSource(m_view, virtualViewId);
392 if (TextUtils.isEmpty(node.getText()) && TextUtils.isEmpty(node.getContentDescription()))
393 Log.w(TAG,
"AccessibilityNodeInfo with empty contentDescription: " + virtualViewId);
395 int parentId = QtNativeAccessibility.parentId(virtualViewId);
396 node.setParent(m_view, parentId);
398 Rect screenRect = QtNativeAccessibility.screenRect(virtualViewId);
399 final int offsetX = m_globalOffset[0];
400 final int offsetY = m_globalOffset[1];
401 screenRect.offset(offsetX, offsetY);
402 node.setBoundsInScreen(screenRect);
404 Rect rectInParent = screenRect;
405 Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
406 rectInParent.offset(-parentScreenRect.left, -parentScreenRect.top);
407 node.setBoundsInParent(rectInParent);
410 if (m_focusedVirtualViewId == virtualViewId) {
411 node.setAccessibilityFocused(
true);
412 node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
414 node.setAccessibilityFocused(
false);
415 node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
418 int[]
ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
419 for (
int i = 0;
i <
ids.length; ++
i)
420 node.addChild(m_view,
ids[
i]);
421 if (node.isScrollable()) {
422 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
423 node.setCollectionInfo(
new CollectionInfo(
ids.length, 1,
false));
425 node.setCollectionInfo(CollectionInfo.obtain(
ids.length, 1,
false));
432 private AccessibilityNodeProvider m_nodeProvider =
new AccessibilityNodeProvider()
435 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId)
438 return getNodeForView();
440 return getNodeForVirtualViewId(virtualViewId);
444 public boolean performAction(
int virtualViewId,
int action,
Bundle arguments)
446 boolean handled =
false;
449 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
452 if (m_focusedVirtualViewId != virtualViewId) {
453 m_focusedVirtualViewId = virtualViewId;
456 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
460 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
461 if (m_focusedVirtualViewId == virtualViewId) {
470 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
475 if (virtualViewId ==
View.NO_ID) {
476 return m_view.performAccessibilityAction(action,
arguments);
489 boolean success =
false;
491 case AccessibilityNodeInfo.ACTION_CLICK:
492 success = QtNativeAccessibility.clickAction(virtualViewId);
496 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
497 success = QtNativeAccessibility.scrollForward(virtualViewId);
501 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
502 success = QtNativeAccessibility.scrollBackward(virtualViewId);
void notifyLocationChange(int viewId)
void notifyScrolledEvent(int viewId)
AccessibilityNodeProvider getAccessibilityNodeProvider(View host)
void notifyObjectHide(int viewId, int parentId)
boolean performActionForVirtualViewId(int virtualViewId, int action, Bundle arguments)
boolean sendAccessibilityEvent(AccessibilityEvent event)
boolean sendEventForVirtualViewId(int virtualViewId, int eventType)
void notifyObjectFocus(int viewId)
void notifyValueChanged(int viewId, String value)
static final int INVALID_ID
QtAccessibilityDelegate(Activity activity, ViewGroup layout, QtActivityDelegate activityDelegate)
void invalidateVirtualViewId(int virtualViewId)
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
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent