Prefetching can be interupted by other service requests.

Slow prefetch requests would block user interactive requests, creating
noticeable sluggishness and unresponsiveness in accessibility services,
especially on the web.

Let's make it so a user interactive requests stops prefetching.
We can't interupt an API call, but we can stop in between API calls.

On the service side, we have to seprate the prefetch callbacks from the
find callback. And we have to make it asynchrnous. It does dispatch into
the main thread, so the AccessibilityCache can remain single threaded.

When the calls are interupted on the application side, returnPendingFindAccessibilityNodeInfosInPrefetch checks the find requests that are waiting in the queue, to see if they can be addressed by the prefetch results. If they can be, we don't have to call into potentially imperformance application code.

Bug:30969887
Test: Performance measurements, tried it out by hand to see if their are any bugs. CTSAccessibility*

Change-Id: Ia8f1152afa3987f262f37ed4583775acdd32db43
This commit is contained in:
Qasid Ahmad Sadiq
2020-12-14 18:36:27 -08:00
parent 312aae751f
commit 103dd32a74
5 changed files with 320 additions and 221 deletions

View File

@@ -113,6 +113,8 @@ public final class AccessibilityInteractionController {
private AddNodeInfosForViewId mAddNodeInfosForViewId;
private List<Message> mPendingFindNodeByIdMessages;
@GuardedBy("mLock")
private int mNumActiveRequestPreparers;
@GuardedBy("mLock")
@@ -128,6 +130,7 @@ public final class AccessibilityInteractionController {
mViewRootImpl = viewRootImpl;
mPrefetcher = new AccessibilityNodePrefetcher();
mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
mPendingFindNodeByIdMessages = new ArrayList<>();
}
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
@@ -177,6 +180,7 @@ public final class AccessibilityInteractionController {
args.arg4 = arguments;
message.obj = args;
mPendingFindNodeByIdMessages.add(message);
scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
@@ -315,6 +319,8 @@ public final class AccessibilityInteractionController {
}
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
mPendingFindNodeByIdMessages.remove(message);
final int flags = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
@@ -329,22 +335,58 @@ public final class AccessibilityInteractionController {
args.recycle();
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
View rootView = null;
AccessibilityNodeInfo rootNode = null;
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
mPrefetcher.prefetchAccessibilityNodeInfos(
root, virtualDescendantId, flags, infos, arguments);
rootView = findViewByAccessibilityId(accessibilityViewId);
if (rootView != null && isShown(rootView)) {
rootNode = populateAccessibilityNodeInfoForView(
rootView, arguments, virtualDescendantId);
}
} finally {
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, interactiveRegion);
updateInfoForViewportAndReturnFindNodeResult(
rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
callback, interactionId, spec, interactiveRegion);
}
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
mPrefetcher.prefetchAccessibilityNodeInfos(
rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode),
virtualDescendantId, flags, infos);
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewPort(infos, spec, interactiveRegion);
returnPrefetchResult(interactionId, infos, callback);
returnPendingFindAccessibilityNodeInfosInPrefetch(infos);
}
private AccessibilityNodeInfo populateAccessibilityNodeInfoForView(
View view, Bundle arguments, int virtualViewId) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
// Determine if we'll be populating extra data
final String extraDataRequested = (arguments == null) ? null
: arguments.getString(EXTRA_DATA_REQUESTED_KEY);
AccessibilityNodeInfo root = null;
if (provider == null) {
root = view.createAccessibilityNodeInfo();
if (root != null) {
if (extraDataRequested != null) {
view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments);
}
}
} else {
root = provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
if (extraDataRequested != null) {
provider.addExtraDataToAccessibilityNodeInfo(
virtualViewId, root, extraDataRequested, arguments);
}
}
}
return root;
}
public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
@@ -402,6 +444,7 @@ public final class AccessibilityInteractionController {
mAddNodeInfosForViewId.reset();
}
} finally {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, interactiveRegion);
}
@@ -484,6 +527,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, interactiveRegion);
}
@@ -575,6 +619,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
focused, callback, interactionId, spec, interactiveRegion);
}
@@ -629,6 +674,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
next, callback, interactionId, spec, interactiveRegion);
}
@@ -785,33 +831,6 @@ public final class AccessibilityInteractionController {
}
}
private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
MagnificationSpec spec) {
if (infos == null) {
return;
}
final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
}
}
}
private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
Region interactiveRegion) {
if (interactiveRegion == null || infos == null) {
return;
}
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
}
}
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
Region interactiveRegion) {
if (interactiveRegion == null || info == null) {
@@ -832,17 +851,6 @@ public final class AccessibilityInteractionController {
return false;
}
private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) {
if (infos == null || shouldBypassAdjustBoundsInScreen()) {
return;
}
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
final AccessibilityNodeInfo info = infos.get(i);
adjustBoundsInScreenIfNeeded(info);
}
}
private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
if (info == null || shouldBypassAdjustBoundsInScreen()) {
return;
@@ -890,17 +898,6 @@ public final class AccessibilityInteractionController {
return screenMatrix == null || screenMatrix.isIdentity();
}
private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) {
if (infos == null || shouldBypassAssociateLeashedParent()) {
return;
}
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
final AccessibilityNodeInfo info = infos.get(i);
associateLeashedParentIfNeeded(info);
}
}
private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
if (info == null || shouldBypassAssociateLeashedParent()) {
return;
@@ -974,18 +971,46 @@ public final class AccessibilityInteractionController {
return (appScale != 1.0f || (spec != null && !spec.isNop()));
}
private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec,
Region interactiveRegion) {
for (int i = 0; i < infos.size(); i++) {
updateInfoForViewPort(infos.get(i), spec, interactiveRegion);
}
}
private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec,
Region interactiveRegion) {
associateLeashedParentIfNeeded(info);
applyScreenMatrixIfNeeded(info);
adjustBoundsInScreenIfNeeded(info);
// To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
// then impact the visibility result, we need to adjust visibility before apply scale.
adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
}
private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
IAccessibilityInteractionConnectionCallback callback, int interactionId,
MagnificationSpec spec, Region interactiveRegion) {
if (infos != null) {
updateInfosForViewPort(infos, spec, interactiveRegion);
}
returnFindNodesResult(infos, callback, interactionId);
}
private void returnFindNodeResult(AccessibilityNodeInfo info,
IAccessibilityInteractionConnectionCallback callback,
int interactionId) {
try {
callback.setFindAccessibilityNodeInfoResult(info, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
}
private void returnFindNodesResult(List<AccessibilityNodeInfo> infos,
IAccessibilityInteractionConnectionCallback callback, int interactionId) {
try {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
associateLeashedParentIfNeeded(infos);
applyScreenMatrixIfNeeded(infos);
adjustBoundsInScreenIfNeeded(infos);
// To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
// then impact the visibility result, we need to adjust visibility before apply scale.
adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
if (infos != null) {
infos.clear();
@@ -995,22 +1020,49 @@ public final class AccessibilityInteractionController {
}
}
private void returnPendingFindAccessibilityNodeInfosInPrefetch(
List<AccessibilityNodeInfo> infos) {
for (Message pendingMessage : mPendingFindNodeByIdMessages) {
SomeArgs args = (SomeArgs) pendingMessage.obj;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
final long nodeId =
AccessibilityNodeInfo.makeNodeId(accessibilityViewId, virtualDescendantId);
for (int i = 0; i < infos.size(); i++) {
AccessibilityNodeInfo info = infos.get(i);
if (info.getSourceNodeId() == nodeId) {
returnFindNodeResult(
AccessibilityNodeInfo.obtain(info), callback, interactionId);
mHandler.removeMessages(
PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
pendingMessage.obj);
args.recycle();
break;
}
}
}
mPendingFindNodeByIdMessages.clear();
}
private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos,
IAccessibilityInteractionConnectionCallback callback) {
if (infos.size() > 0) {
try {
callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - other side isn't too bothered if this doesn't arrive */
}
}
}
private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
IAccessibilityInteractionConnectionCallback callback, int interactionId,
MagnificationSpec spec, Region interactiveRegion) {
try {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
associateLeashedParentIfNeeded(info);
applyScreenMatrixIfNeeded(info);
adjustBoundsInScreenIfNeeded(info);
// To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
// then impact the visibility result, we need to adjust visibility before apply scale.
adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
callback.setFindAccessibilityNodeInfoResult(info, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
updateInfoForViewPort(info, spec, interactiveRegion);
returnFindNodeResult(info, callback, interactionId);
}
private boolean handleClickableSpanActionUiThread(
@@ -1053,20 +1105,11 @@ public final class AccessibilityInteractionController {
private final ArrayList<View> mTempViewList = new ArrayList<View>();
public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
// Determine if we'll be populating extra data
final String extraDataRequested = (arguments == null) ? null
: arguments.getString(EXTRA_DATA_REQUESTED_KEY);
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
if (extraDataRequested != null) {
view.addExtraDataToAccessibilityNodeInfo(
root, extraDataRequested, arguments);
}
outInfos.add(root);
public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
if (root != null) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider == null) {
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
@@ -1076,16 +1119,7 @@ public final class AccessibilityInteractionController {
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfRealNode(view, outInfos);
}
}
} else {
final AccessibilityNodeInfo root =
provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
if (extraDataRequested != null) {
provider.addExtraDataToAccessibilityNodeInfo(
virtualViewId, root, extraDataRequested, arguments);
}
outInfos.add(root);
} else {
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
@@ -1096,13 +1130,19 @@ public final class AccessibilityInteractionController {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
}
if (ENFORCE_NODE_TREE_CONSISTENT) {
enforceNodeTreeConsistent(outInfos);
if (ENFORCE_NODE_TREE_CONSISTENT) {
enforceNodeTreeConsistent(root, outInfos);
}
}
}
private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
private boolean shouldStopPrefetching(List prefetchededInfos) {
return mHandler.hasUserInteractiveMessagesWaiting()
|| prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE;
}
private void enforceNodeTreeConsistent(
AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) {
LongSparseArray<AccessibilityNodeInfo> nodeMap =
new LongSparseArray<AccessibilityNodeInfo>();
final int nodeCount = nodes.size();
@@ -1113,7 +1153,6 @@ public final class AccessibilityInteractionController {
// If the nodes are a tree it does not matter from
// which node we start to search for the root.
AccessibilityNodeInfo root = nodeMap.valueAt(0);
AccessibilityNodeInfo parent = root;
while (parent != null) {
root = parent;
@@ -1180,9 +1219,11 @@ public final class AccessibilityInteractionController {
private void prefetchPredecessorsOfRealNode(View view,
List<AccessibilityNodeInfo> outInfos) {
if (shouldStopPrefetching(outInfos)) {
return;
}
ViewParent parent = view.getParentForAccessibility();
while (parent instanceof View
&& outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
View parentView = (View) parent;
AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
if (info != null) {
@@ -1194,6 +1235,9 @@ public final class AccessibilityInteractionController {
private void prefetchSiblingsOfRealNode(View current,
List<AccessibilityNodeInfo> outInfos) {
if (shouldStopPrefetching(outInfos)) {
return;
}
ViewParent parent = current.getParentForAccessibility();
if (parent instanceof ViewGroup) {
ViewGroup parentGroup = (ViewGroup) parent;
@@ -1203,7 +1247,7 @@ public final class AccessibilityInteractionController {
parentGroup.addChildrenForAccessibility(children);
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (shouldStopPrefetching(outInfos)) {
return;
}
View child = children.get(i);
@@ -1231,7 +1275,7 @@ public final class AccessibilityInteractionController {
private void prefetchDescendantsOfRealNode(View root,
List<AccessibilityNodeInfo> outInfos) {
if (!(root instanceof ViewGroup)) {
if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
return;
}
HashMap<View, AccessibilityNodeInfo> addedChildren =
@@ -1242,7 +1286,7 @@ public final class AccessibilityInteractionController {
root.addChildrenForAccessibility(children);
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (shouldStopPrefetching(outInfos)) {
return;
}
View child = children.get(i);
@@ -1267,7 +1311,7 @@ public final class AccessibilityInteractionController {
} finally {
children.clear();
}
if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (!shouldStopPrefetching(outInfos)) {
for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
View addedChild = entry.getKey();
AccessibilityNodeInfo virtualRoot = entry.getValue();
@@ -1289,7 +1333,7 @@ public final class AccessibilityInteractionController {
long parentNodeId = root.getParentNodeId();
int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (shouldStopPrefetching(outInfos)) {
return;
}
final int virtualDescendantId =
@@ -1334,7 +1378,7 @@ public final class AccessibilityInteractionController {
if (parent != null) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (shouldStopPrefetching(outInfos)) {
return;
}
final long childNodeId = parent.getChildId(i);
@@ -1359,7 +1403,7 @@ public final class AccessibilityInteractionController {
final int initialOutInfosSize = outInfos.size();
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (shouldStopPrefetching(outInfos)) {
return;
}
final long childNodeId = root.getChildId(i);
@@ -1369,7 +1413,7 @@ public final class AccessibilityInteractionController {
outInfos.add(child);
}
}
if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
if (!shouldStopPrefetching(outInfos)) {
final int addedChildCount = outInfos.size() - initialOutInfosSize;
for (int i = 0; i < addedChildCount; i++) {
AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
@@ -1478,6 +1522,10 @@ public final class AccessibilityInteractionController {
boolean hasAccessibilityCallback(Message message) {
return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
}
boolean hasUserInteractiveMessagesWaiting() {
return hasMessagesOrCallbacks();
}
}
private final class AddNodeInfosForViewId implements Predicate<View> {

View File

@@ -23,7 +23,9 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -123,6 +125,12 @@ public final class AccessibilityInteractionClient
private Message mSameThreadMessage;
private int mInteractionIdWaitingForPrefetchResult;
private int mConnectionIdWaitingForPrefetchResult;
private String[] mPackageNamesForNextPrefetchResult;
private final Handler mMainHandler;
private Runnable mPrefetchResultRunnable;
/**
* @return The client for the current thread.
*/
@@ -197,6 +205,7 @@ public final class AccessibilityInteractionClient
private AccessibilityInteractionClient() {
/* reducing constructor visibility */
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
@@ -451,16 +460,16 @@ public final class AccessibilityInteractionClient
Binder.restoreCallingIdentity(identityToken);
}
if (packageNames != null) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
bypassCache, packageNames);
if (infos != null && !infos.isEmpty()) {
for (int i = 1; i < infos.size(); i++) {
infos.get(i).recycle();
}
return infos.get(0);
AccessibilityNodeInfo info =
getFindAccessibilityNodeInfoResultAndClear(interactionId);
if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
&& info != null) {
setInteractionWaitingForPrefetchResult(interactionId, connectionId,
packageNames);
}
finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
bypassCache, packageNames);
return info;
}
} else {
if (DEBUG) {
@@ -474,6 +483,15 @@ public final class AccessibilityInteractionClient
return null;
}
private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId,
String[] packageNames) {
synchronized (mInstanceLock) {
mInteractionIdWaitingForPrefetchResult = interactionId;
mConnectionIdWaitingForPrefetchResult = connectionId;
mPackageNamesForNextPrefetchResult = packageNames;
}
}
private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
return accessibilityWindowId + "/"
+ AccessibilityNodeInfo.idToString(accessibilityNodeId);
@@ -828,6 +846,26 @@ public final class AccessibilityInteractionClient
}
}
/**
* {@inheritDoc}
*/
@Override
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
if (mPrefetchResultRunnable != null) {
mMainHandler.removeCallbacks(mPrefetchResultRunnable);
mPrefetchResultRunnable = null;
}
if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) {
mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos(
infos, mConnectionIdWaitingForPrefetchResult, false,
mPackageNamesForNextPrefetchResult);
mMainHandler.post(mPrefetchResultRunnable);
}
}
}
/**
* Gets the result of a request to perform an accessibility action.
*

View File

@@ -46,6 +46,15 @@ oneway interface IAccessibilityInteractionConnectionCallback {
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
int interactionId);
/**
* Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s.
*
* @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
* @param infos The result {@link AccessibilityNodeInfo}s.
*/
void setPrefetchAccessibilityNodeInfoResult(
in List<AccessibilityNodeInfo> infos, int interactionId);
/**
* Sets the result of a request to perform an accessibility action.
*

View File

@@ -33,9 +33,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.Arrays;
import java.util.List;
/**
* Tests for AccessibilityInteractionClient
*/
@@ -65,7 +62,7 @@ public class AccessibilityInteractionClientTest {
final long accessibilityNodeId = 0x4321L;
AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
mMockConnection.mInfoToReturn = nodeFromConnection;
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
@@ -75,7 +72,7 @@ public class AccessibilityInteractionClientTest {
}
private static class MockConnection extends AccessibilityServiceConnectionImpl {
List<AccessibilityNodeInfo> mInfosToReturn;
AccessibilityNodeInfo mInfoToReturn;
@Override
public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
@@ -83,7 +80,7 @@ public class AccessibilityInteractionClientTest {
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
Bundle arguments) {
try {
callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}

View File

@@ -40,10 +40,14 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
private final IAccessibilityInteractionConnectionCallback mServiceCallback;
private final IAccessibilityInteractionConnection mConnectionWithReplacementActions;
private final int mInteractionId;
private final int mNodeWithReplacementActionsInteractionId;
private final Object mLock = new Object();
@GuardedBy("mLock")
List<AccessibilityNodeInfo> mNodesWithReplacementActions;
private boolean mRequestForNodeWithReplacementActionFailed;
@GuardedBy("mLock")
AccessibilityNodeInfo mNodeWithReplacementActions;
@GuardedBy("mLock")
List<AccessibilityNodeInfo> mNodesFromOriginalWindow;
@@ -51,18 +55,8 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
@GuardedBy("mLock")
AccessibilityNodeInfo mNodeFromOriginalWindow;
// Keep track of whether or not we've been called back for a single node
@GuardedBy("mLock")
boolean mSingleNodeCallbackHappened;
// Keep track of whether or not we've been called back for multiple node
@GuardedBy("mLock")
boolean mMultiNodeCallbackHappened;
// We shouldn't get any more callbacks after we've called back the original service, but
// keep track to make sure we catch such strange things
@GuardedBy("mLock")
boolean mDone;
List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow;
public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback,
IAccessibilityInteractionConnection connectionWithReplacementActions,
@@ -70,19 +64,20 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
mServiceCallback = serviceCallback;
mConnectionWithReplacementActions = connectionWithReplacementActions;
mInteractionId = interactionId;
mNodeWithReplacementActionsInteractionId = interactionId + 1;
// Request the root node of the replacing window
final long identityToken = Binder.clearCallingIdentity();
try {
mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId(
AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0,
AccessibilityNodeInfo.ROOT_NODE_ID, null,
mNodeWithReplacementActionsInteractionId, this, 0,
interrogatingPid, interrogatingTid, null, null);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
}
// Pretend we already got a (null) list of replacement nodes
mMultiNodeCallbackHappened = true;
mRequestForNodeWithReplacementActionFailed = true;
} finally {
Binder.restoreCallingIdentity(identityToken);
}
@@ -90,46 +85,73 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
@Override
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
boolean readyForCallback;
synchronized(mLock) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
mNodeFromOriginalWindow = info;
} else if (interactionId == mNodeWithReplacementActionsInteractionId) {
mNodeWithReplacementActions = info;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
return;
}
mSingleNodeCallbackHappened = true;
readyForCallback = mMultiNodeCallbackHappened;
}
if (readyForCallback) {
replaceInfoActionsAndCallService();
}
replaceInfoActionsAndCallServiceIfReady();
}
@Override
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
boolean callbackForSingleNode;
boolean callbackForMultipleNodes;
synchronized(mLock) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
mNodesFromOriginalWindow = infos;
} else if (interactionId == mInteractionId + 1) {
mNodesWithReplacementActions = infos;
} else if (interactionId == mNodeWithReplacementActionsInteractionId) {
setNodeWithReplacementActionsFromList(infos);
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
return;
}
callbackForSingleNode = mSingleNodeCallbackHappened;
callbackForMultipleNodes = mMultiNodeCallbackHappened;
mMultiNodeCallbackHappened = true;
}
if (callbackForSingleNode) {
replaceInfoActionsAndCallServiceIfReady();
}
@Override
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId)
throws RemoteException {
synchronized (mLock) {
if (interactionId == mInteractionId) {
mPrefetchedNodesFromOriginalWindow = infos;
} else {
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
return;
}
}
replaceInfoActionsAndCallServiceIfReady();
}
private void replaceInfoActionsAndCallServiceIfReady() {
boolean originalAndReplacementCallsHaveHappened = false;
synchronized (mLock) {
originalAndReplacementCallsHaveHappened = mNodeWithReplacementActions != null
&& (mNodeFromOriginalWindow != null
|| mNodesFromOriginalWindow != null
|| mPrefetchedNodesFromOriginalWindow != null);
originalAndReplacementCallsHaveHappened
|= mRequestForNodeWithReplacementActionFailed;
}
if (originalAndReplacementCallsHaveHappened) {
replaceInfoActionsAndCallService();
}
if (callbackForMultipleNodes) {
replaceInfosActionsAndCallService();
replacePrefetchInfosActionsAndCallService();
}
}
private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) {
for (int i = 0; i < infos.size(); i++) {
AccessibilityNodeInfo info = infos.get(i);
if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) {
mNodeWithReplacementActions = info;
}
}
}
@@ -143,18 +165,10 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
private void replaceInfoActionsAndCallService() {
final AccessibilityNodeInfo nodeToReturn;
synchronized (mLock) {
if (mDone) {
if (DEBUG) {
Slog.e(LOG_TAG, "Extra callback");
}
return;
}
if (mNodeFromOriginalWindow != null) {
replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
}
recycleReplaceActionNodesLocked();
nodeToReturn = mNodeFromOriginalWindow;
mDone = true;
}
try {
mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
@@ -168,21 +182,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
private void replaceInfosActionsAndCallService() {
final List<AccessibilityNodeInfo> nodesToReturn;
synchronized (mLock) {
if (mDone) {
if (DEBUG) {
Slog.e(LOG_TAG, "Extra callback");
}
return;
}
if (mNodesFromOriginalWindow != null) {
for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i));
}
}
recycleReplaceActionNodesLocked();
nodesToReturn = (mNodesFromOriginalWindow == null)
? null : new ArrayList<>(mNodesFromOriginalWindow);
mDone = true;
nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow);
}
try {
mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
@@ -193,6 +193,31 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
}
private void replacePrefetchInfosActionsAndCallService() {
final List<AccessibilityNodeInfo> nodesToReturn;
synchronized (mLock) {
nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow);
}
try {
mServiceCallback.setPrefetchAccessibilityNodeInfoResult(nodesToReturn, mInteractionId);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
}
}
}
@GuardedBy("mLock")
private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) {
if (infos != null) {
for (int i = 0; i < infos.size(); i++) {
replaceActionsOnInfoLocked(infos.get(i));
}
}
return (infos == null)
? null : new ArrayList<>(infos);
}
@GuardedBy("mLock")
private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) {
info.removeAllActions();
@@ -204,40 +229,22 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
info.setDismissable(false);
// We currently only replace actions for the root node
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
&& mNodesWithReplacementActions != null) {
// This list should always contain a single node with the root ID
for (int i = 0; i < mNodesWithReplacementActions.size(); i++) {
AccessibilityNodeInfo nodeWithReplacementActions =
mNodesWithReplacementActions.get(i);
if (nodeWithReplacementActions.getSourceNodeId()
== AccessibilityNodeInfo.ROOT_NODE_ID) {
List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList();
if (actions != null) {
for (int j = 0; j < actions.size(); j++) {
info.addAction(actions.get(j));
}
// The PIP needs to be able to take accessibility focus
info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
info.setClickable(nodeWithReplacementActions.isClickable());
info.setFocusable(nodeWithReplacementActions.isFocusable());
info.setContextClickable(nodeWithReplacementActions.isContextClickable());
info.setScrollable(nodeWithReplacementActions.isScrollable());
info.setLongClickable(nodeWithReplacementActions.isLongClickable());
info.setDismissable(nodeWithReplacementActions.isDismissable());
&& mNodeWithReplacementActions != null) {
List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList();
if (actions != null) {
for (int j = 0; j < actions.size(); j++) {
info.addAction(actions.get(j));
}
// The PIP needs to be able to take accessibility focus
info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
info.setClickable(mNodeWithReplacementActions.isClickable());
info.setFocusable(mNodeWithReplacementActions.isFocusable());
info.setContextClickable(mNodeWithReplacementActions.isContextClickable());
info.setScrollable(mNodeWithReplacementActions.isScrollable());
info.setLongClickable(mNodeWithReplacementActions.isLongClickable());
info.setDismissable(mNodeWithReplacementActions.isDismissable());
}
}
@GuardedBy("mLock")
private void recycleReplaceActionNodesLocked() {
if (mNodesWithReplacementActions == null) return;
for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) {
AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i);
nodeWithReplacementAction.recycle();
}
mNodesWithReplacementActions = null;
}
}