Skip to content

Commit 0d04e24

Browse files
committed
Improving accessibility APIs used for UI automation.
1. UiTestAutomationBridge was accessing the root node in the active window by tracking the accessibility event stream and keeping the last active window changing event. Now the bridge is stateless and the root node is fetched by passing special window and view id with the request to the system. 2. AccessibilityNodeInfos that are cached were not finished, i.e. not sealed, causing exception when trying to access their children or rpedecessors. 3. AccessibilityManagerService was not properly restoring its state after the UI automation bridge disconnects from it. I particular the devices was still in explore by touch mode event if no services are enabled and the sutomation bridge is disconnected. 4. ViewRootImpl for the focused window now fires accessibility events when accessibility is enabled to allow accessibility services to determine the current user location. 5. Several missing null checks in ViewRootImpl are fixed since there were scenraios in which a NPE can occur. 6. Update the internal window content querying tests. 7. ViewRootImpl was firing one extra focus event. bug:6009813 bug:6026952 Change-Id: Ib2e058d64538ecc268f9ef7a8f36ead047868a05
1 parent b6ad5b1 commit 0d04e24

File tree

8 files changed

+213
-153
lines changed

8 files changed

+213
-153
lines changed

core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ interface IAccessibilityServiceConnection {
3030
void setServiceInfo(in AccessibilityServiceInfo info);
3131

3232
/**
33-
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
33+
* Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id.
3434
*
3535
* @param accessibilityWindowId A unique window id. Use
36-
* {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
36+
* {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
3737
* to query the currently active window.
3838
* @param accessibilityNodeId A unique view id or virtual descendant id from
3939
* where to start the search. Use
40-
* {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
40+
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
4141
* to start from the root.
4242
* @param interactionId The id of the interaction for matching with the callback result.
4343
* @param callback Callback which to receive the result.
@@ -49,17 +49,16 @@ interface IAccessibilityServiceConnection {
4949
IAccessibilityInteractionConnectionCallback callback, long threadId);
5050

5151
/**
52-
* Finds {@link AccessibilityNodeInfo}s by View text. The match is case
53-
* insensitive containment. The search is performed in the window whose
54-
* id is specified and starts from the node whose accessibility id is
55-
* specified.
52+
* Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
53+
* The match is case insensitive containment. The search is performed in the window
54+
* whose id is specified and starts from the node whose accessibility id is specified.
5655
*
5756
* @param accessibilityWindowId A unique window id. Use
58-
* {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
57+
* {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
5958
* to query the currently active window.
6059
* @param accessibilityNodeId A unique view id or virtual descendant id from
6160
* where to start the search. Use
62-
* {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
61+
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
6362
* to start from the root.
6463
* @param text The searched text.
6564
* @param interactionId The id of the interaction for matching with the callback result.
@@ -72,16 +71,16 @@ interface IAccessibilityServiceConnection {
7271
long threadId);
7372

7473
/**
75-
* Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
76-
* the window whose id is specified and starts from the node whose accessibility
77-
* id is specified.
74+
* Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search
75+
* is performed in the window whose id is specified and starts from the node whose
76+
* accessibility id is specified.
7877
*
7978
* @param accessibilityWindowId A unique window id. Use
80-
* {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
79+
* {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
8180
* to query the currently active window.
8281
* @param accessibilityNodeId A unique view id or virtual descendant id from
8382
* where to start the search. Use
84-
* {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
83+
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
8584
* to start from the root.
8685
* @param id The id of the node.
8786
* @param interactionId The id of the interaction for matching with the callback result.
@@ -94,14 +93,15 @@ interface IAccessibilityServiceConnection {
9493
long threadId);
9594

9695
/**
97-
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
96+
* Performs an accessibility action on an
97+
* {@link android.view.accessibility.AccessibilityNodeInfo}.
9898
*
9999
* @param accessibilityWindowId A unique window id. Use
100-
* {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
100+
* {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
101101
* to query the currently active window.
102102
* @param accessibilityNodeId A unique view id or virtual descendant id from
103103
* where to start the search. Use
104-
* {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
104+
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
105105
* to start from the root.
106106
* @param action The action to perform.
107107
* @param interactionId The id of the interaction for matching with the callback result.

core/java/android/accessibilityservice/UiTestAutomationBridge.java

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ public class UiTestAutomationBridge {
4949

5050
private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
5151

52-
public static final int ACTIVE_WINDOW_ID = -1;
52+
private static final int TIMEOUT_REGISTER_SERVICE = 5000;
5353

54-
public static final long ROOT_NODE_ID = -1;
54+
public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
5555

56-
private static final int TIMEOUT_REGISTER_SERVICE = 5000;
56+
public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
57+
58+
public static final int UNDEFINED = -1;
5759

5860
private final Object mLock = new Object();
5961

@@ -63,8 +65,6 @@ public class UiTestAutomationBridge {
6365

6466
private AccessibilityEvent mLastEvent;
6567

66-
private AccessibilityEvent mLastWindowStateChangeEvent;
67-
6868
private volatile boolean mWaitingForEventDelivery;
6969

7070
private volatile boolean mUnprocessedEventAvailable;
@@ -141,17 +141,8 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
141141
synchronized (mLock) {
142142
while (true) {
143143
mLastEvent = AccessibilityEvent.obtain(event);
144-
145-
final int eventType = event.getEventType();
146-
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
147-
|| eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
148-
if (mLastWindowStateChangeEvent != null) {
149-
mLastWindowStateChangeEvent.recycle();
150-
}
151-
mLastWindowStateChangeEvent = mLastEvent;
152-
}
153-
154144
if (!mWaitingForEventDelivery) {
145+
mLock.notifyAll();
155146
break;
156147
}
157148
if (!mUnprocessedEventAvailable) {
@@ -294,6 +285,43 @@ public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable co
294285
}
295286
}
296287

288+
/**
289+
* Waits for the accessibility event stream to become idle, which is not to
290+
* have received a new accessibility event within <code>idleTimeout</code>,
291+
* and do so within a maximal global timeout as specified by
292+
* <code>globalTimeout</code>.
293+
*
294+
* @param idleTimeout The timeout between two event to consider the device idle.
295+
* @param globalTimeout The maximal global timeout in which to wait for idle.
296+
*/
297+
public void waitForIdle(long idleTimeout, long globalTimeout) {
298+
final long startTimeMillis = SystemClock.uptimeMillis();
299+
long lastEventTime = (mLastEvent != null)
300+
? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
301+
synchronized (mLock) {
302+
while (true) {
303+
final long currentTimeMillis = SystemClock.uptimeMillis();
304+
final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
305+
if (sinceLastEventTimeMillis > idleTimeout) {
306+
return;
307+
}
308+
if (mLastEvent != null) {
309+
lastEventTime = mLastEvent.getEventTime();
310+
}
311+
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
312+
final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
313+
if (remainingTimeMillis <= 0) {
314+
return;
315+
}
316+
try {
317+
mLock.wait(idleTimeout);
318+
} catch (InterruptedException e) {
319+
/* ignore */
320+
}
321+
}
322+
}
323+
}
324+
297325
/**
298326
* Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
299327
* window. The search is performed from the root node.
@@ -310,8 +338,8 @@ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveW
310338
/**
311339
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
312340
*
313-
* @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
314-
* to query the currently active window.
341+
* @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
342+
* the currently active window.
315343
* @param accessibilityNodeId A unique view id or virtual descendant id for
316344
* which to search.
317345
* @return The current window scale, where zero means a failure.
@@ -341,10 +369,10 @@ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int
341369
* the window whose id is specified and starts from the node whose accessibility
342370
* id is specified.
343371
*
344-
* @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
345-
* to query the currently active window.
372+
* @param accessibilityWindowId A unique window id. Use
373+
* {@link #ACTIVE_WINDOW_ID} to query the currently active window.
346374
* @param accessibilityNodeId A unique view id or virtual descendant id from
347-
* where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
375+
* where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
348376
* @return The current window scale, where zero means a failure.
349377
*/
350378
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
@@ -374,8 +402,8 @@ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindo
374402
* id is specified and starts from the node whose accessibility id is
375403
* specified.
376404
*
377-
* @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
378-
* to query the currently active window.
405+
* @param accessibilityWindowId A unique window id. Use
406+
* {@link #ACTIVE_WINDOW_ID} to query the currently active window.
379407
* @param accessibilityNodeId A unique view id or virtual descendant id from
380408
* where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
381409
* @param text The searched text.
@@ -406,8 +434,8 @@ public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId
406434
/**
407435
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
408436
*
409-
* @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
410-
* to query the currently active window.
437+
* @param accessibilityWindowId A unique window id. Use
438+
* {@link #ACTIVE_WINDOW_ID} to query the currently active window.
411439
* @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
412440
* @param action The action to perform.
413441
* @return Whether the action was performed.
@@ -427,16 +455,16 @@ public boolean performAccessibilityAction(int accessibilityWindowId, long access
427455
* @return The root info.
428456
*/
429457
public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
430-
synchronized (mLock) {
431-
if (mLastWindowStateChangeEvent != null) {
432-
return mLastWindowStateChangeEvent.getSource();
433-
}
434-
}
435-
return null;
458+
// Cache the id to avoid locking
459+
final int connectionId = mConnectionId;
460+
ensureValidConnection(connectionId);
461+
return AccessibilityInteractionClient.getInstance()
462+
.findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
463+
ROOT_NODE_ID);
436464
}
437465

438466
private void ensureValidConnection(int connectionId) {
439-
if (connectionId == AccessibilityInteractionClient.NO_ID) {
467+
if (connectionId == UNDEFINED) {
440468
throw new IllegalStateException("UiAutomationService not connected."
441469
+ " Did you call #register()?");
442470
}

core/java/android/view/AccessibilityNodeInfoCache.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package android.view;
1818

19+
import android.os.Process;
20+
import android.util.Log;
1921
import android.util.LongSparseArray;
2022
import android.view.accessibility.AccessibilityEvent;
2123
import android.view.accessibility.AccessibilityNodeInfo;
@@ -30,7 +32,11 @@
3032
*/
3133
public class AccessibilityNodeInfoCache {
3234

33-
private final boolean ENABLED = true;
35+
private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName();
36+
37+
private static final boolean ENABLED = true;
38+
39+
private static final boolean DEBUG = false;
3440

3541
/**
3642
* @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
@@ -95,6 +101,7 @@ private AccessibilityNodeInfoCache() {
95101
public void onAccessibilityEvent(AccessibilityEvent event) {
96102
final int eventType = event.getEventType();
97103
switch (eventType) {
104+
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
98105
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
99106
case AccessibilityEvent.TYPE_VIEW_SCROLLED:
100107
clear();
@@ -117,7 +124,14 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
117124
*/
118125
public AccessibilityNodeInfo get(long accessibilityNodeId) {
119126
if (ENABLED) {
120-
return mCacheImpl.get(accessibilityNodeId);
127+
if (DEBUG) {
128+
AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
129+
Log.i(LOG_TAG, "Process: " + Process.myPid() +
130+
" get(" + accessibilityNodeId + ") = " + info);
131+
return info;
132+
} else {
133+
return mCacheImpl.get(accessibilityNodeId);
134+
}
121135
} else {
122136
return null;
123137
}
@@ -131,6 +145,10 @@ public AccessibilityNodeInfo get(long accessibilityNodeId) {
131145
*/
132146
public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
133147
if (ENABLED) {
148+
if (DEBUG) {
149+
Log.i(LOG_TAG, "Process: " + Process.myPid()
150+
+ " put(" + accessibilityNodeId + ", " + info + ")");
151+
}
134152
mCacheImpl.put(accessibilityNodeId, info);
135153
}
136154
}
@@ -156,6 +174,10 @@ public boolean containsKey(long accessibilityNodeId) {
156174
*/
157175
public void remove(long accessibilityNodeId) {
158176
if (ENABLED) {
177+
if (DEBUG) {
178+
Log.i(LOG_TAG, "Process: " + Process.myPid()
179+
+ " remove(" + accessibilityNodeId + ")");
180+
}
159181
mCacheImpl.remove(accessibilityNodeId);
160182
}
161183
}
@@ -165,6 +187,9 @@ public void remove(long accessibilityNodeId) {
165187
*/
166188
public void clear() {
167189
if (ENABLED) {
190+
if (DEBUG) {
191+
Log.i(LOG_TAG, "Process: " + Process.myPid() + "clear()");
192+
}
168193
mCacheImpl.clear();
169194
}
170195
}

0 commit comments

Comments
 (0)