Merge "Service requests can interrupt node prefetching" into sc-dev
This commit is contained in:
@@ -86,6 +86,12 @@ public final class AccessibilityInteractionController {
|
|||||||
// accessibility from hanging
|
// accessibility from hanging
|
||||||
private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
|
private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
|
||||||
|
|
||||||
|
// Callbacks should have the same configuration of the flags below to allow satisfying a pending
|
||||||
|
// node request on prefetch
|
||||||
|
private static final int FLAGS_AFFECTING_REPORTED_DATA =
|
||||||
|
AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
|
||||||
|
| AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
|
||||||
|
|
||||||
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
|
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
|
||||||
new ArrayList<AccessibilityNodeInfo>();
|
new ArrayList<AccessibilityNodeInfo>();
|
||||||
|
|
||||||
@@ -113,6 +119,9 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
private AddNodeInfosForViewId mAddNodeInfosForViewId;
|
private AddNodeInfosForViewId mAddNodeInfosForViewId;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private ArrayList<Message> mPendingFindNodeByIdMessages;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private int mNumActiveRequestPreparers;
|
private int mNumActiveRequestPreparers;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@@ -128,6 +137,7 @@ public final class AccessibilityInteractionController {
|
|||||||
mViewRootImpl = viewRootImpl;
|
mViewRootImpl = viewRootImpl;
|
||||||
mPrefetcher = new AccessibilityNodePrefetcher();
|
mPrefetcher = new AccessibilityNodePrefetcher();
|
||||||
mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
|
mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
|
||||||
|
mPendingFindNodeByIdMessages = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
|
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
|
||||||
@@ -177,7 +187,11 @@ public final class AccessibilityInteractionController {
|
|||||||
args.arg4 = arguments;
|
args.arg4 = arguments;
|
||||||
message.obj = args;
|
message.obj = args;
|
||||||
|
|
||||||
scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
|
synchronized (mLock) {
|
||||||
|
mPendingFindNodeByIdMessages.add(message);
|
||||||
|
scheduleMessage(message, interrogatingPid, interrogatingTid,
|
||||||
|
CONSIDER_REQUEST_PREPARERS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -315,6 +329,9 @@ public final class AccessibilityInteractionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
|
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mPendingFindNodeByIdMessages.remove(message);
|
||||||
|
}
|
||||||
final int flags = message.arg1;
|
final int flags = message.arg1;
|
||||||
|
|
||||||
SomeArgs args = (SomeArgs) message.obj;
|
SomeArgs args = (SomeArgs) message.obj;
|
||||||
@@ -329,22 +346,69 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
args.recycle();
|
args.recycle();
|
||||||
|
|
||||||
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
|
View requestedView = null;
|
||||||
infos.clear();
|
AccessibilityNodeInfo requestedNode = null;
|
||||||
try {
|
try {
|
||||||
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
|
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
|
||||||
final View root = findViewByAccessibilityId(accessibilityViewId);
|
requestedView = findViewByAccessibilityId(accessibilityViewId);
|
||||||
if (root != null && isShown(root)) {
|
if (requestedView != null && isShown(requestedView)) {
|
||||||
mPrefetcher.prefetchAccessibilityNodeInfos(
|
requestedNode = populateAccessibilityNodeInfoForView(
|
||||||
root, virtualDescendantId, flags, infos, arguments);
|
requestedView, arguments, virtualDescendantId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
updateInfosForViewportAndReturnFindNodeResult(
|
updateInfoForViewportAndReturnFindNodeResult(
|
||||||
infos, callback, interactionId, spec, interactiveRegion);
|
requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
|
||||||
|
callback, interactionId, spec, interactiveRegion);
|
||||||
}
|
}
|
||||||
|
ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
|
||||||
|
infos.clear();
|
||||||
|
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
|
||||||
|
requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode),
|
||||||
|
virtualDescendantId, flags, infos);
|
||||||
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
||||||
|
updateInfosForViewPort(infos, spec, interactiveRegion);
|
||||||
|
final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
|
||||||
|
getSatisfiedRequestInPrefetch(
|
||||||
|
requestedNode == null ? null : requestedNode, infos, flags);
|
||||||
|
|
||||||
|
if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode != requestedNode) {
|
||||||
|
infos.remove(satisfiedRequest.mSatisfiedRequestNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnPrefetchResult(interactionId, infos, callback);
|
||||||
|
|
||||||
|
if (satisfiedRequest != null) {
|
||||||
|
returnFindNodeResult(satisfiedRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
|
||||||
@@ -403,6 +467,7 @@ public final class AccessibilityInteractionController {
|
|||||||
mAddNodeInfosForViewId.reset();
|
mAddNodeInfosForViewId.reset();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
||||||
updateInfosForViewportAndReturnFindNodeResult(
|
updateInfosForViewportAndReturnFindNodeResult(
|
||||||
infos, callback, interactionId, spec, interactiveRegion);
|
infos, callback, interactionId, spec, interactiveRegion);
|
||||||
}
|
}
|
||||||
@@ -485,6 +550,7 @@ public final class AccessibilityInteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
||||||
updateInfosForViewportAndReturnFindNodeResult(
|
updateInfosForViewportAndReturnFindNodeResult(
|
||||||
infos, callback, interactionId, spec, interactiveRegion);
|
infos, callback, interactionId, spec, interactiveRegion);
|
||||||
}
|
}
|
||||||
@@ -576,6 +642,7 @@ public final class AccessibilityInteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
||||||
updateInfoForViewportAndReturnFindNodeResult(
|
updateInfoForViewportAndReturnFindNodeResult(
|
||||||
focused, callback, interactionId, spec, interactiveRegion);
|
focused, callback, interactionId, spec, interactiveRegion);
|
||||||
}
|
}
|
||||||
@@ -630,6 +697,7 @@ public final class AccessibilityInteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
||||||
updateInfoForViewportAndReturnFindNodeResult(
|
updateInfoForViewportAndReturnFindNodeResult(
|
||||||
next, callback, interactionId, spec, interactiveRegion);
|
next, callback, interactionId, spec, interactiveRegion);
|
||||||
}
|
}
|
||||||
@@ -786,33 +854,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,
|
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
|
||||||
Region interactiveRegion) {
|
Region interactiveRegion) {
|
||||||
if (interactiveRegion == null || info == null) {
|
if (interactiveRegion == null || info == null) {
|
||||||
@@ -833,17 +874,6 @@ public final class AccessibilityInteractionController {
|
|||||||
return false;
|
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) {
|
private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
|
||||||
if (info == null || shouldBypassAdjustBoundsInScreen()) {
|
if (info == null || shouldBypassAdjustBoundsInScreen()) {
|
||||||
return;
|
return;
|
||||||
@@ -891,17 +921,6 @@ public final class AccessibilityInteractionController {
|
|||||||
return screenMatrix == null || screenMatrix.isIdentity();
|
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) {
|
private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
|
||||||
if (info == null || shouldBypassAssociateLeashedParent()) {
|
if (info == null || shouldBypassAssociateLeashedParent()) {
|
||||||
return;
|
return;
|
||||||
@@ -975,18 +994,59 @@ public final class AccessibilityInteractionController {
|
|||||||
return (appScale != 1.0f || (spec != null && !spec.isNop()));
|
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,
|
private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
|
||||||
IAccessibilityInteractionConnectionCallback callback, int interactionId,
|
IAccessibilityInteractionConnectionCallback callback, int interactionId,
|
||||||
MagnificationSpec spec, Region interactiveRegion) {
|
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 returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest
|
||||||
|
satisfiedRequest) {
|
||||||
|
try {
|
||||||
|
final AccessibilityNodeInfo info = satisfiedRequest.mSatisfiedRequestNode;
|
||||||
|
final IAccessibilityInteractionConnectionCallback callback =
|
||||||
|
satisfiedRequest.mSatisfiedRequestCallback;
|
||||||
|
final int interactionId = satisfiedRequest.mSatisfiedRequestInteractionId;
|
||||||
|
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 {
|
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);
|
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
|
||||||
if (infos != null) {
|
if (infos != null) {
|
||||||
infos.clear();
|
infos.clear();
|
||||||
@@ -996,22 +1056,74 @@ public final class AccessibilityInteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SatisfiedFindAccessibilityNodeByAccessibilityIdRequest getSatisfiedRequestInPrefetch(
|
||||||
|
AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags) {
|
||||||
|
SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = null;
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) {
|
||||||
|
final Message pendingMessage = mPendingFindNodeByIdMessages.get(i);
|
||||||
|
final int pendingFlags = pendingMessage.arg1;
|
||||||
|
if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA)
|
||||||
|
!= (flags & FLAGS_AFFECTING_REPORTED_DATA)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SomeArgs args = (SomeArgs) pendingMessage.obj;
|
||||||
|
final int accessibilityViewId = args.argi1;
|
||||||
|
final int virtualDescendantId = args.argi2;
|
||||||
|
|
||||||
|
final AccessibilityNodeInfo satisfiedRequestNode = nodeWithIdFromList(requestedNode,
|
||||||
|
infos, AccessibilityNodeInfo.makeNodeId(
|
||||||
|
accessibilityViewId, virtualDescendantId));
|
||||||
|
|
||||||
|
if (satisfiedRequestNode != null) {
|
||||||
|
mHandler.removeMessages(
|
||||||
|
PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
|
||||||
|
pendingMessage.obj);
|
||||||
|
final IAccessibilityInteractionConnectionCallback satisfiedRequestCallback =
|
||||||
|
(IAccessibilityInteractionConnectionCallback) args.arg1;
|
||||||
|
final int satisfiedRequestInteractionId = args.argi3;
|
||||||
|
satisfiedRequest = new SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
|
||||||
|
satisfiedRequestNode, satisfiedRequestCallback,
|
||||||
|
satisfiedRequestInteractionId);
|
||||||
|
args.recycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mPendingFindNodeByIdMessages.clear();
|
||||||
|
return satisfiedRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo requestedNode,
|
||||||
|
List<AccessibilityNodeInfo> infos, long nodeId) {
|
||||||
|
if (requestedNode != null && requestedNode.getSourceNodeId() == nodeId) {
|
||||||
|
return requestedNode;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < infos.size(); j++) {
|
||||||
|
AccessibilityNodeInfo info = infos.get(j);
|
||||||
|
if (info.getSourceNodeId() == nodeId) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
|
||||||
IAccessibilityInteractionConnectionCallback callback, int interactionId,
|
IAccessibilityInteractionConnectionCallback callback, int interactionId,
|
||||||
MagnificationSpec spec, Region interactiveRegion) {
|
MagnificationSpec spec, Region interactiveRegion) {
|
||||||
try {
|
updateInfoForViewPort(info, spec, interactiveRegion);
|
||||||
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
|
returnFindNodeResult(info, callback, interactionId);
|
||||||
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 */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleClickableSpanActionUiThread(
|
private boolean handleClickableSpanActionUiThread(
|
||||||
@@ -1054,56 +1166,45 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
private final ArrayList<View> mTempViewList = new ArrayList<View>();
|
private final ArrayList<View> mTempViewList = new ArrayList<View>();
|
||||||
|
|
||||||
public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
|
public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
|
||||||
List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
|
int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) {
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
|
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) {
|
if (provider == null) {
|
||||||
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
|
||||||
if (root != null) {
|
prefetchPredecessorsOfRealNode(view, outInfos);
|
||||||
if (extraDataRequested != null) {
|
}
|
||||||
view.addExtraDataToAccessibilityNodeInfo(
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
|
||||||
root, extraDataRequested, arguments);
|
prefetchSiblingsOfRealNode(view, outInfos);
|
||||||
}
|
}
|
||||||
outInfos.add(root);
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
|
prefetchDescendantsOfRealNode(view, outInfos);
|
||||||
prefetchPredecessorsOfRealNode(view, outInfos);
|
|
||||||
}
|
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
|
|
||||||
prefetchSiblingsOfRealNode(view, outInfos);
|
|
||||||
}
|
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
|
|
||||||
prefetchDescendantsOfRealNode(view, outInfos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final AccessibilityNodeInfo root =
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
|
||||||
provider.createAccessibilityNodeInfo(virtualViewId);
|
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
|
||||||
if (root != null) {
|
}
|
||||||
if (extraDataRequested != null) {
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
|
||||||
provider.addExtraDataToAccessibilityNodeInfo(
|
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
|
||||||
virtualViewId, root, extraDataRequested, arguments);
|
}
|
||||||
}
|
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
|
||||||
outInfos.add(root);
|
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
|
|
||||||
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
|
|
||||||
}
|
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
|
|
||||||
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
|
|
||||||
}
|
|
||||||
if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
|
|
||||||
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ENFORCE_NODE_TREE_CONSISTENT) {
|
if (ENFORCE_NODE_TREE_CONSISTENT) {
|
||||||
enforceNodeTreeConsistent(outInfos);
|
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 =
|
LongSparseArray<AccessibilityNodeInfo> nodeMap =
|
||||||
new LongSparseArray<AccessibilityNodeInfo>();
|
new LongSparseArray<AccessibilityNodeInfo>();
|
||||||
final int nodeCount = nodes.size();
|
final int nodeCount = nodes.size();
|
||||||
@@ -1114,7 +1215,6 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
// If the nodes are a tree it does not matter from
|
// If the nodes are a tree it does not matter from
|
||||||
// which node we start to search for the root.
|
// which node we start to search for the root.
|
||||||
AccessibilityNodeInfo root = nodeMap.valueAt(0);
|
|
||||||
AccessibilityNodeInfo parent = root;
|
AccessibilityNodeInfo parent = root;
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
root = parent;
|
root = parent;
|
||||||
@@ -1181,9 +1281,11 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
private void prefetchPredecessorsOfRealNode(View view,
|
private void prefetchPredecessorsOfRealNode(View view,
|
||||||
List<AccessibilityNodeInfo> outInfos) {
|
List<AccessibilityNodeInfo> outInfos) {
|
||||||
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ViewParent parent = view.getParentForAccessibility();
|
ViewParent parent = view.getParentForAccessibility();
|
||||||
while (parent instanceof View
|
while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
|
||||||
&& outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
|
||||||
View parentView = (View) parent;
|
View parentView = (View) parent;
|
||||||
AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
|
AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
@@ -1195,6 +1297,9 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
private void prefetchSiblingsOfRealNode(View current,
|
private void prefetchSiblingsOfRealNode(View current,
|
||||||
List<AccessibilityNodeInfo> outInfos) {
|
List<AccessibilityNodeInfo> outInfos) {
|
||||||
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ViewParent parent = current.getParentForAccessibility();
|
ViewParent parent = current.getParentForAccessibility();
|
||||||
if (parent instanceof ViewGroup) {
|
if (parent instanceof ViewGroup) {
|
||||||
ViewGroup parentGroup = (ViewGroup) parent;
|
ViewGroup parentGroup = (ViewGroup) parent;
|
||||||
@@ -1204,7 +1309,7 @@ public final class AccessibilityInteractionController {
|
|||||||
parentGroup.addChildrenForAccessibility(children);
|
parentGroup.addChildrenForAccessibility(children);
|
||||||
final int childCount = children.size();
|
final int childCount = children.size();
|
||||||
for (int i = 0; i < childCount; i++) {
|
for (int i = 0; i < childCount; i++) {
|
||||||
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
View child = children.get(i);
|
View child = children.get(i);
|
||||||
@@ -1232,7 +1337,7 @@ public final class AccessibilityInteractionController {
|
|||||||
|
|
||||||
private void prefetchDescendantsOfRealNode(View root,
|
private void prefetchDescendantsOfRealNode(View root,
|
||||||
List<AccessibilityNodeInfo> outInfos) {
|
List<AccessibilityNodeInfo> outInfos) {
|
||||||
if (!(root instanceof ViewGroup)) {
|
if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HashMap<View, AccessibilityNodeInfo> addedChildren =
|
HashMap<View, AccessibilityNodeInfo> addedChildren =
|
||||||
@@ -1243,7 +1348,7 @@ public final class AccessibilityInteractionController {
|
|||||||
root.addChildrenForAccessibility(children);
|
root.addChildrenForAccessibility(children);
|
||||||
final int childCount = children.size();
|
final int childCount = children.size();
|
||||||
for (int i = 0; i < childCount; i++) {
|
for (int i = 0; i < childCount; i++) {
|
||||||
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
View child = children.get(i);
|
View child = children.get(i);
|
||||||
@@ -1268,7 +1373,7 @@ public final class AccessibilityInteractionController {
|
|||||||
} finally {
|
} finally {
|
||||||
children.clear();
|
children.clear();
|
||||||
}
|
}
|
||||||
if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (!shouldStopPrefetching(outInfos)) {
|
||||||
for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
|
for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
|
||||||
View addedChild = entry.getKey();
|
View addedChild = entry.getKey();
|
||||||
AccessibilityNodeInfo virtualRoot = entry.getValue();
|
AccessibilityNodeInfo virtualRoot = entry.getValue();
|
||||||
@@ -1290,7 +1395,7 @@ public final class AccessibilityInteractionController {
|
|||||||
long parentNodeId = root.getParentNodeId();
|
long parentNodeId = root.getParentNodeId();
|
||||||
int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
|
int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
|
||||||
while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
|
while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
|
||||||
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final int virtualDescendantId =
|
final int virtualDescendantId =
|
||||||
@@ -1335,7 +1440,7 @@ public final class AccessibilityInteractionController {
|
|||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
final int childCount = parent.getChildCount();
|
final int childCount = parent.getChildCount();
|
||||||
for (int i = 0; i < childCount; i++) {
|
for (int i = 0; i < childCount; i++) {
|
||||||
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final long childNodeId = parent.getChildId(i);
|
final long childNodeId = parent.getChildId(i);
|
||||||
@@ -1360,7 +1465,7 @@ public final class AccessibilityInteractionController {
|
|||||||
final int initialOutInfosSize = outInfos.size();
|
final int initialOutInfosSize = outInfos.size();
|
||||||
final int childCount = root.getChildCount();
|
final int childCount = root.getChildCount();
|
||||||
for (int i = 0; i < childCount; i++) {
|
for (int i = 0; i < childCount; i++) {
|
||||||
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (shouldStopPrefetching(outInfos)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final long childNodeId = root.getChildId(i);
|
final long childNodeId = root.getChildId(i);
|
||||||
@@ -1370,7 +1475,7 @@ public final class AccessibilityInteractionController {
|
|||||||
outInfos.add(child);
|
outInfos.add(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
|
if (!shouldStopPrefetching(outInfos)) {
|
||||||
final int addedChildCount = outInfos.size() - initialOutInfosSize;
|
final int addedChildCount = outInfos.size() - initialOutInfosSize;
|
||||||
for (int i = 0; i < addedChildCount; i++) {
|
for (int i = 0; i < addedChildCount; i++) {
|
||||||
AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
|
AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
|
||||||
@@ -1479,6 +1584,10 @@ public final class AccessibilityInteractionController {
|
|||||||
boolean hasAccessibilityCallback(Message message) {
|
boolean hasAccessibilityCallback(Message message) {
|
||||||
return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
|
return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasUserInteractiveMessagesWaiting() {
|
||||||
|
return hasMessagesOrCallbacks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class AddNodeInfosForViewId implements Predicate<View> {
|
private final class AddNodeInfosForViewId implements Predicate<View> {
|
||||||
@@ -1515,4 +1624,19 @@ public final class AccessibilityInteractionController {
|
|||||||
mInterrogatingTid = interrogatingTid;
|
mInterrogatingTid = interrogatingTid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SatisfiedFindAccessibilityNodeByAccessibilityIdRequest {
|
||||||
|
final AccessibilityNodeInfo mSatisfiedRequestNode;
|
||||||
|
final IAccessibilityInteractionConnectionCallback mSatisfiedRequestCallback;
|
||||||
|
final int mSatisfiedRequestInteractionId;
|
||||||
|
|
||||||
|
SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
|
||||||
|
AccessibilityNodeInfo satisfiedRequestNode,
|
||||||
|
IAccessibilityInteractionConnectionCallback satisfiedRequestCallback,
|
||||||
|
int satisfiedRequestInteractionId) {
|
||||||
|
mSatisfiedRequestNode = satisfiedRequestNode;
|
||||||
|
mSatisfiedRequestCallback = satisfiedRequestCallback;
|
||||||
|
mSatisfiedRequestInteractionId = satisfiedRequestInteractionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ public final class AccessibilityInteractionClient
|
|||||||
|
|
||||||
private Message mSameThreadMessage;
|
private Message mSameThreadMessage;
|
||||||
|
|
||||||
|
private int mInteractionIdWaitingForPrefetchResult = -1;
|
||||||
|
private int mConnectionIdWaitingForPrefetchResult;
|
||||||
|
private String[] mPackageNamesForNextPrefetchResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The client for the current thread.
|
* @return The client for the current thread.
|
||||||
*/
|
*/
|
||||||
@@ -494,25 +498,27 @@ public final class AccessibilityInteractionClient
|
|||||||
Binder.restoreCallingIdentity(identityToken);
|
Binder.restoreCallingIdentity(identityToken);
|
||||||
}
|
}
|
||||||
if (packageNames != null) {
|
if (packageNames != null) {
|
||||||
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
|
AccessibilityNodeInfo info =
|
||||||
interactionId);
|
getFindAccessibilityNodeInfoResultAndClear(interactionId);
|
||||||
if (mAccessibilityManager != null
|
if (mAccessibilityManager != null
|
||||||
&& mAccessibilityManager.isAccessibilityTracingEnabled()) {
|
&& mAccessibilityManager.isAccessibilityTracingEnabled()) {
|
||||||
logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId",
|
logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId",
|
||||||
"InteractionId:" + interactionId + ";Result: " + infos
|
"InteractionId:" + interactionId + ";Result: " + info
|
||||||
+ ";connectionId=" + connectionId + ";accessibilityWindowId="
|
+ ";connectionId=" + connectionId
|
||||||
+ accessibilityWindowId + ";accessibilityNodeId="
|
+ ";accessibilityWindowId="
|
||||||
+ accessibilityNodeId + ";bypassCache=" + bypassCache
|
+ accessibilityWindowId + ";accessibilityNodeId="
|
||||||
+ ";prefetchFlags=" + prefetchFlags + ";arguments=" + arguments);
|
+ accessibilityNodeId + ";bypassCache=" + bypassCache
|
||||||
|
+ ";prefetchFlags=" + prefetchFlags
|
||||||
|
+ ";arguments=" + arguments);
|
||||||
}
|
}
|
||||||
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
|
if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
|
||||||
|
&& info != null) {
|
||||||
|
setInteractionWaitingForPrefetchResult(interactionId, connectionId,
|
||||||
|
packageNames);
|
||||||
|
}
|
||||||
|
finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
|
||||||
bypassCache, packageNames);
|
bypassCache, packageNames);
|
||||||
if (infos != null && !infos.isEmpty()) {
|
return info;
|
||||||
for (int i = 1; i < infos.size(); i++) {
|
|
||||||
infos.get(i).recycle();
|
|
||||||
}
|
|
||||||
return infos.get(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -526,6 +532,15 @@ public final class AccessibilityInteractionClient
|
|||||||
return null;
|
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) {
|
private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
|
||||||
return accessibilityWindowId + "/"
|
return accessibilityWindowId + "/"
|
||||||
+ AccessibilityNodeInfo.idToString(accessibilityNodeId);
|
+ AccessibilityNodeInfo.idToString(accessibilityNodeId);
|
||||||
@@ -923,6 +938,46 @@ public final class AccessibilityInteractionClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
|
||||||
|
int interactionId) {
|
||||||
|
int interactionIdWaitingForPrefetchResultCopy = -1;
|
||||||
|
int connectionIdWaitingForPrefetchResultCopy = -1;
|
||||||
|
String[] packageNamesForNextPrefetchResultCopy = null;
|
||||||
|
|
||||||
|
if (infos.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mInstanceLock) {
|
||||||
|
if (mInteractionIdWaitingForPrefetchResult == interactionId) {
|
||||||
|
interactionIdWaitingForPrefetchResultCopy = mInteractionIdWaitingForPrefetchResult;
|
||||||
|
connectionIdWaitingForPrefetchResultCopy =
|
||||||
|
mConnectionIdWaitingForPrefetchResult;
|
||||||
|
if (mPackageNamesForNextPrefetchResult != null) {
|
||||||
|
packageNamesForNextPrefetchResultCopy =
|
||||||
|
new String[mPackageNamesForNextPrefetchResult.length];
|
||||||
|
for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) {
|
||||||
|
packageNamesForNextPrefetchResultCopy[i] =
|
||||||
|
mPackageNamesForNextPrefetchResult[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactionIdWaitingForPrefetchResultCopy == interactionId) {
|
||||||
|
finalizeAndCacheAccessibilityNodeInfos(
|
||||||
|
infos, connectionIdWaitingForPrefetchResultCopy, false,
|
||||||
|
packageNamesForNextPrefetchResultCopy);
|
||||||
|
} else if (DEBUG) {
|
||||||
|
Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped "
|
||||||
|
+ infos.size() + " nodes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the result of a request to perform an accessibility action.
|
* Gets the result of a request to perform an accessibility action.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ oneway interface IAccessibilityInteractionConnectionCallback {
|
|||||||
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
|
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
|
||||||
int interactionId);
|
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.
|
* Sets the result of a request to perform an accessibility action.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for AccessibilityInteractionClient
|
* Tests for AccessibilityInteractionClient
|
||||||
*/
|
*/
|
||||||
@@ -65,7 +62,7 @@ public class AccessibilityInteractionClientTest {
|
|||||||
final long accessibilityNodeId = 0x4321L;
|
final long accessibilityNodeId = 0x4321L;
|
||||||
AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
|
AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
|
||||||
nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
|
nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
|
||||||
mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
|
mMockConnection.mInfoToReturn = nodeFromConnection;
|
||||||
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||||
AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
|
AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
|
||||||
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
|
MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
|
||||||
@@ -75,7 +72,7 @@ public class AccessibilityInteractionClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class MockConnection extends AccessibilityServiceConnectionImpl {
|
private static class MockConnection extends AccessibilityServiceConnectionImpl {
|
||||||
List<AccessibilityNodeInfo> mInfosToReturn;
|
AccessibilityNodeInfo mInfoToReturn;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
|
public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
|
||||||
@@ -83,7 +80,7 @@ public class AccessibilityInteractionClientTest {
|
|||||||
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
|
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
|
||||||
Bundle arguments) {
|
Bundle arguments) {
|
||||||
try {
|
try {
|
||||||
callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
|
callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,29 +40,34 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
|||||||
private final IAccessibilityInteractionConnectionCallback mServiceCallback;
|
private final IAccessibilityInteractionConnectionCallback mServiceCallback;
|
||||||
private final IAccessibilityInteractionConnection mConnectionWithReplacementActions;
|
private final IAccessibilityInteractionConnection mConnectionWithReplacementActions;
|
||||||
private final int mInteractionId;
|
private final int mInteractionId;
|
||||||
|
private final int mNodeWithReplacementActionsInteractionId;
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
List<AccessibilityNodeInfo> mNodesWithReplacementActions;
|
private boolean mReplacementNodeIsReadyOrFailed;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
AccessibilityNodeInfo mNodeWithReplacementActions;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
List<AccessibilityNodeInfo> mNodesFromOriginalWindow;
|
List<AccessibilityNodeInfo> mNodesFromOriginalWindow;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
boolean mSetFindNodeFromOriginalWindowCalled = false;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
AccessibilityNodeInfo mNodeFromOriginalWindow;
|
AccessibilityNodeInfo mNodeFromOriginalWindow;
|
||||||
|
|
||||||
// Keep track of whether or not we've been called back for a single node
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
boolean mSingleNodeCallbackHappened;
|
boolean mSetFindNodesFromOriginalWindowCalled = false;
|
||||||
|
|
||||||
// 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")
|
@GuardedBy("mLock")
|
||||||
boolean mDone;
|
List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
boolean mSetPrefetchFromOriginalWindowCalled = false;
|
||||||
|
|
||||||
|
|
||||||
public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback,
|
public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback,
|
||||||
IAccessibilityInteractionConnection connectionWithReplacementActions,
|
IAccessibilityInteractionConnection connectionWithReplacementActions,
|
||||||
@@ -70,19 +75,20 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
|||||||
mServiceCallback = serviceCallback;
|
mServiceCallback = serviceCallback;
|
||||||
mConnectionWithReplacementActions = connectionWithReplacementActions;
|
mConnectionWithReplacementActions = connectionWithReplacementActions;
|
||||||
mInteractionId = interactionId;
|
mInteractionId = interactionId;
|
||||||
|
mNodeWithReplacementActionsInteractionId = interactionId + 1;
|
||||||
|
|
||||||
// Request the root node of the replacing window
|
// Request the root node of the replacing window
|
||||||
final long identityToken = Binder.clearCallingIdentity();
|
final long identityToken = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId(
|
mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId(
|
||||||
AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0,
|
AccessibilityNodeInfo.ROOT_NODE_ID, null,
|
||||||
|
mNodeWithReplacementActionsInteractionId, this, 0,
|
||||||
interrogatingPid, interrogatingTid, null, null);
|
interrogatingPid, interrogatingTid, null, null);
|
||||||
} catch (RemoteException re) {
|
} catch (RemoteException re) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
|
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
|
||||||
}
|
}
|
||||||
// Pretend we already got a (null) list of replacement nodes
|
mReplacementNodeIsReadyOrFailed = true;
|
||||||
mMultiNodeCallbackHappened = true;
|
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(identityToken);
|
Binder.restoreCallingIdentity(identityToken);
|
||||||
}
|
}
|
||||||
@@ -90,46 +96,67 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
|
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
|
||||||
boolean readyForCallback;
|
synchronized (mLock) {
|
||||||
synchronized(mLock) {
|
|
||||||
if (interactionId == mInteractionId) {
|
if (interactionId == mInteractionId) {
|
||||||
mNodeFromOriginalWindow = info;
|
mNodeFromOriginalWindow = info;
|
||||||
|
mSetFindNodeFromOriginalWindowCalled = true;
|
||||||
|
} else if (interactionId == mNodeWithReplacementActionsInteractionId) {
|
||||||
|
mNodeWithReplacementActions = info;
|
||||||
|
mReplacementNodeIsReadyOrFailed = true;
|
||||||
} else {
|
} else {
|
||||||
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSingleNodeCallbackHappened = true;
|
|
||||||
readyForCallback = mMultiNodeCallbackHappened;
|
|
||||||
}
|
|
||||||
if (readyForCallback) {
|
|
||||||
replaceInfoActionsAndCallService();
|
|
||||||
}
|
}
|
||||||
|
replaceInfoActionsAndCallServiceIfReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
|
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
|
||||||
int interactionId) {
|
int interactionId) {
|
||||||
boolean callbackForSingleNode;
|
synchronized (mLock) {
|
||||||
boolean callbackForMultipleNodes;
|
|
||||||
synchronized(mLock) {
|
|
||||||
if (interactionId == mInteractionId) {
|
if (interactionId == mInteractionId) {
|
||||||
mNodesFromOriginalWindow = infos;
|
mNodesFromOriginalWindow = infos;
|
||||||
} else if (interactionId == mInteractionId + 1) {
|
mSetFindNodesFromOriginalWindowCalled = true;
|
||||||
mNodesWithReplacementActions = infos;
|
} else if (interactionId == mNodeWithReplacementActionsInteractionId) {
|
||||||
|
setNodeWithReplacementActionsFromList(infos);
|
||||||
|
mReplacementNodeIsReadyOrFailed = true;
|
||||||
} else {
|
} else {
|
||||||
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callbackForSingleNode = mSingleNodeCallbackHappened;
|
|
||||||
callbackForMultipleNodes = mMultiNodeCallbackHappened;
|
|
||||||
mMultiNodeCallbackHappened = true;
|
|
||||||
}
|
}
|
||||||
if (callbackForSingleNode) {
|
replaceInfoActionsAndCallServiceIfReady();
|
||||||
replaceInfoActionsAndCallService();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
|
||||||
|
int interactionId)
|
||||||
|
throws RemoteException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (interactionId == mInteractionId) {
|
||||||
|
mPrefetchedNodesFromOriginalWindow = infos;
|
||||||
|
mSetPrefetchFromOriginalWindowCalled = true;
|
||||||
|
} else {
|
||||||
|
Slog.e(LOG_TAG, "Callback with unexpected interactionId");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (callbackForMultipleNodes) {
|
replaceInfoActionsAndCallServiceIfReady();
|
||||||
replaceInfosActionsAndCallService();
|
}
|
||||||
|
|
||||||
|
private void replaceInfoActionsAndCallServiceIfReady() {
|
||||||
|
replaceInfoActionsAndCallService();
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,55 +169,81 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
|||||||
|
|
||||||
private void replaceInfoActionsAndCallService() {
|
private void replaceInfoActionsAndCallService() {
|
||||||
final AccessibilityNodeInfo nodeToReturn;
|
final AccessibilityNodeInfo nodeToReturn;
|
||||||
|
boolean doCallback = false;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mDone) {
|
doCallback = mReplacementNodeIsReadyOrFailed
|
||||||
if (DEBUG) {
|
&& mSetFindNodeFromOriginalWindowCalled;
|
||||||
Slog.e(LOG_TAG, "Extra callback");
|
if (doCallback && mNodeFromOriginalWindow != null) {
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mNodeFromOriginalWindow != null) {
|
|
||||||
replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
|
replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
|
||||||
|
mSetFindNodeFromOriginalWindowCalled = false;
|
||||||
}
|
}
|
||||||
recycleReplaceActionNodesLocked();
|
|
||||||
nodeToReturn = mNodeFromOriginalWindow;
|
nodeToReturn = mNodeFromOriginalWindow;
|
||||||
mDone = true;
|
|
||||||
}
|
}
|
||||||
try {
|
if (doCallback) {
|
||||||
mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
|
try {
|
||||||
} catch (RemoteException re) {
|
mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
|
||||||
if (DEBUG) {
|
} catch (RemoteException re) {
|
||||||
Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
|
if (DEBUG) {
|
||||||
|
Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void replaceInfosActionsAndCallService() {
|
private void replaceInfosActionsAndCallService() {
|
||||||
final List<AccessibilityNodeInfo> nodesToReturn;
|
List<AccessibilityNodeInfo> nodesToReturn = null;
|
||||||
|
boolean doCallback = false;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mDone) {
|
doCallback = mReplacementNodeIsReadyOrFailed
|
||||||
|
&& mSetFindNodesFromOriginalWindowCalled;
|
||||||
|
if (doCallback) {
|
||||||
|
nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow);
|
||||||
|
mSetFindNodesFromOriginalWindowCalled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doCallback) {
|
||||||
|
try {
|
||||||
|
mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
|
||||||
|
} catch (RemoteException re) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.e(LOG_TAG, "Extra callback");
|
Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
try {
|
}
|
||||||
mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
|
|
||||||
} catch (RemoteException re) {
|
private void replacePrefetchInfosActionsAndCallService() {
|
||||||
if (DEBUG) {
|
List<AccessibilityNodeInfo> nodesToReturn = null;
|
||||||
Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
|
boolean doCallback = false;
|
||||||
|
synchronized (mLock) {
|
||||||
|
doCallback = mReplacementNodeIsReadyOrFailed
|
||||||
|
&& mSetPrefetchFromOriginalWindowCalled;
|
||||||
|
if (doCallback) {
|
||||||
|
nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow);
|
||||||
|
mSetPrefetchFromOriginalWindowCalled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (doCallback) {
|
||||||
|
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")
|
@GuardedBy("mLock")
|
||||||
@@ -204,40 +257,22 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
|
|||||||
info.setDismissable(false);
|
info.setDismissable(false);
|
||||||
// We currently only replace actions for the root node
|
// We currently only replace actions for the root node
|
||||||
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
|
if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
|
||||||
&& mNodesWithReplacementActions != null) {
|
&& mNodeWithReplacementActions != null) {
|
||||||
// This list should always contain a single node with the root ID
|
List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList();
|
||||||
for (int i = 0; i < mNodesWithReplacementActions.size(); i++) {
|
if (actions != null) {
|
||||||
AccessibilityNodeInfo nodeWithReplacementActions =
|
for (int j = 0; j < actions.size(); j++) {
|
||||||
mNodesWithReplacementActions.get(i);
|
info.addAction(actions.get(j));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,644 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.accessibility;
|
||||||
|
|
||||||
|
|
||||||
|
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
|
||||||
|
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
|
||||||
|
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
|
||||||
|
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.AccessibilityInteractionController;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewRootImpl;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.accessibility.AccessibilityNodeIdManager;
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.view.accessibility.AccessibilityNodeProvider;
|
||||||
|
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that verify expected node and prefetched node results when finding a view by node id. We
|
||||||
|
* send some requests to the controller via View methods to control message timing.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AccessibilityInteractionControllerNodeRequestsTest {
|
||||||
|
private AccessibilityInteractionController mAccessibilityInteractionController;
|
||||||
|
@Mock
|
||||||
|
private IAccessibilityInteractionConnectionCallback mMockClientCallback1;
|
||||||
|
@Mock
|
||||||
|
private IAccessibilityInteractionConnectionCallback mMockClientCallback2;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor;
|
||||||
|
@Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor;
|
||||||
|
|
||||||
|
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1;
|
||||||
|
private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2;
|
||||||
|
|
||||||
|
private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout";
|
||||||
|
private static final String TEXT_VIEW_1_DESCRIPTION = "textView1";
|
||||||
|
private static final String TEXT_VIEW_2_DESCRIPTION = "textView2";
|
||||||
|
|
||||||
|
private TestFrameLayout mFrameLayout;
|
||||||
|
private TestTextView mTextView1;
|
||||||
|
private TestTextView2 mTextView2;
|
||||||
|
|
||||||
|
private boolean mSendClient1RequestForTextAfterTextPrefetched;
|
||||||
|
private boolean mSendClient2RequestForTextAfterTextPrefetched;
|
||||||
|
private boolean mSendRequestForTextAndIncludeUnImportantViews;
|
||||||
|
private boolean mSendClient1RequestForRootAfterTextPrefetched;
|
||||||
|
private int mMockClient1InteractionId;
|
||||||
|
private int mMockClient2InteractionId;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Throwable {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mInstrumentation.runOnMainSync(() -> {
|
||||||
|
final Context context = mInstrumentation.getTargetContext();
|
||||||
|
final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
|
||||||
|
|
||||||
|
mFrameLayout = new TestFrameLayout(context);
|
||||||
|
mTextView1 = new TestTextView(context);
|
||||||
|
mTextView2 = new TestTextView2(context);
|
||||||
|
|
||||||
|
mFrameLayout.addView(mTextView1);
|
||||||
|
mFrameLayout.addView(mTextView2);
|
||||||
|
|
||||||
|
// The controller retrieves views through this manager, and registration happens on
|
||||||
|
// when attached to a window, which we don't have. We can simply reference FrameLayout
|
||||||
|
// with ROOT_NODE_ID
|
||||||
|
AccessibilityNodeIdManager.getInstance().registerViewWithId(
|
||||||
|
mTextView1, mTextView1.getAccessibilityViewId());
|
||||||
|
AccessibilityNodeIdManager.getInstance().registerViewWithId(
|
||||||
|
mTextView2, mTextView2.getAccessibilityViewId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null);
|
||||||
|
|
||||||
|
} catch (WindowManager.BadTokenException e) {
|
||||||
|
// activity isn't running, we will ignore BadTokenException.
|
||||||
|
}
|
||||||
|
|
||||||
|
mAccessibilityInteractionController =
|
||||||
|
new AccessibilityInteractionController(viewRootImpl);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Throwable {
|
||||||
|
AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
|
||||||
|
mTextView1.getAccessibilityViewId());
|
||||||
|
AccessibilityNodeIdManager.getInstance().unregisterViewWithId(
|
||||||
|
mTextView2.getAccessibilityViewId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a basic request for the root node with prefetch flag
|
||||||
|
* {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS}
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants()
|
||||||
|
throws RemoteException {
|
||||||
|
// Request for our FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// Verify we get FrameLayout
|
||||||
|
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
|
||||||
|
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
|
||||||
|
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
|
||||||
|
|
||||||
|
verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
|
||||||
|
mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
|
||||||
|
// The descendants are our two TextViews
|
||||||
|
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
|
||||||
|
assertEquals(2, prefetchedNodes.size());
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
|
||||||
|
assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a basic request for TestTextView1's node with prefetch flag
|
||||||
|
* {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS}
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings()
|
||||||
|
throws RemoteException {
|
||||||
|
// Request for TextView1
|
||||||
|
sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
|
||||||
|
mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// Verify we get TextView1
|
||||||
|
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
|
||||||
|
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
|
||||||
|
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
|
||||||
|
|
||||||
|
// Verify the prefetched sibling of TextView1 is TextView2
|
||||||
|
verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(
|
||||||
|
mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId));
|
||||||
|
// TextView2 is the prefetched sibling
|
||||||
|
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
|
||||||
|
assertEquals(1, prefetchedNodes.size());
|
||||||
|
assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a series of controller requests to prevent prefetching.
|
||||||
|
* Request 1: Client 1 requests the root node
|
||||||
|
* Request 2: When the root node is initialized in
|
||||||
|
* {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)},
|
||||||
|
* Client 2 requests TestTextView1's node
|
||||||
|
*
|
||||||
|
* Request 2 on the queue prevents prefetching for Request 1.
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch()
|
||||||
|
throws RemoteException {
|
||||||
|
mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() {
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
final long nodeId = AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mTextView1.getAccessibilityViewId(),
|
||||||
|
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||||
|
|
||||||
|
// Enqueue a request when this node is found from a different service for
|
||||||
|
// TextView1
|
||||||
|
sendNodeRequestToController(nodeId, mMockClientCallback2,
|
||||||
|
mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Client 1 request for FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// Verify client 1 gets FrameLayout
|
||||||
|
verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult(
|
||||||
|
mFindInfoCaptor.capture(), eq(mMockClient1InteractionId));
|
||||||
|
AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue();
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription());
|
||||||
|
|
||||||
|
// The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo,
|
||||||
|
// meaning prefetching is interrupted and does not even begin for the first request
|
||||||
|
verify(mMockClientCallback1, never())
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
|
||||||
|
|
||||||
|
// Verify client 2 gets TextView1
|
||||||
|
verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult(
|
||||||
|
mFindInfoCaptor.capture(), eq(mMockClient2InteractionId));
|
||||||
|
infoSentToService = mFindInfoCaptor.getValue();
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription());
|
||||||
|
|
||||||
|
// Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS)
|
||||||
|
verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult(
|
||||||
|
mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId));
|
||||||
|
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
|
||||||
|
assertEquals(1, prefetchedNodes.size());
|
||||||
|
assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a series of controller same-service requests to interrupt prefetching and satisfy a
|
||||||
|
* pending node request.
|
||||||
|
* Request 1: Request the root node
|
||||||
|
* Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching,
|
||||||
|
* request TestTextView1's node
|
||||||
|
*
|
||||||
|
* Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks
|
||||||
|
* if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for
|
||||||
|
* TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not
|
||||||
|
* prefetch. TestTextView1 is removed from Request's 1's prefetched results, meaning the list
|
||||||
|
* is empty.
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()
|
||||||
|
throws RemoteException {
|
||||||
|
mSendClient1RequestForTextAfterTextPrefetched = true;
|
||||||
|
|
||||||
|
mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
|
||||||
|
final long nodeId = AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mTextView1.getAccessibilityViewId(),
|
||||||
|
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||||
|
|
||||||
|
if (mSendClient1RequestForTextAfterTextPrefetched) {
|
||||||
|
// Prevent a loop when processing second request
|
||||||
|
mSendClient1RequestForTextAfterTextPrefetched = false;
|
||||||
|
// TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
|
||||||
|
// same-client request for TextView1
|
||||||
|
sendNodeRequestToController(nodeId, mMockClientCallback1,
|
||||||
|
++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Client 1 requests FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
// Flush out all messages
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// When TextView1 is prefetched for FrameLayout, we put a message on the queue in
|
||||||
|
// TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get
|
||||||
|
// two node results for FrameLayout and TextView1.
|
||||||
|
verify(mMockClientCallback1, times(2))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
|
||||||
|
|
||||||
|
List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription());
|
||||||
|
|
||||||
|
// The controller will look at FrameLayout's prefetched nodes and find matching nodes in
|
||||||
|
// pending requests. The prefetched TextView1 matches the second request. This is removed
|
||||||
|
// from the first request's prefetch list, which is now empty. The second
|
||||||
|
// request was removed from queue and prefetching for this request never occurred.
|
||||||
|
verify(mMockClientCallback1, never())
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
|
||||||
|
eq(mMockClient1InteractionId - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a series of controller same-service requests to interrupt prefetching and satisfy a
|
||||||
|
* pending node request.
|
||||||
|
* Request 1: Request the root node
|
||||||
|
* Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching,
|
||||||
|
* request the root node
|
||||||
|
*
|
||||||
|
* Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks
|
||||||
|
* if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for
|
||||||
|
* the root node. Request 2 is fulfilled, so it is removed from queue and does not
|
||||||
|
* prefetch.
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindRoot_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg()
|
||||||
|
throws RemoteException {
|
||||||
|
mSendClient1RequestForRootAfterTextPrefetched = true;
|
||||||
|
|
||||||
|
mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
|
||||||
|
final long nodeId = AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mFrameLayout.getAccessibilityViewId(),
|
||||||
|
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||||
|
|
||||||
|
if (mSendClient1RequestForRootAfterTextPrefetched) {
|
||||||
|
// Prevent a loop when processing second request
|
||||||
|
mSendClient1RequestForRootAfterTextPrefetched = false;
|
||||||
|
// TextView1 is prefetched here after the FrameLayout is found. Now enqueue a
|
||||||
|
// same-client request for FrameLayout
|
||||||
|
sendNodeRequestToController(nodeId, mMockClientCallback1,
|
||||||
|
++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Client 1 requests FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
// Flush out all messages
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// When TextView1 is prefetched for FrameLayout, we put a message on the queue in
|
||||||
|
// TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets
|
||||||
|
// two node results for FrameLayout and TextView1.
|
||||||
|
verify(mMockClientCallback1, times(2))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
|
||||||
|
|
||||||
|
List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues();
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription());
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription());
|
||||||
|
|
||||||
|
// The controller will look at FrameLayout's prefetched nodes and find matching nodes in
|
||||||
|
// pending requests. The first requested node (FrameLayout) is also checked, and this
|
||||||
|
// satifies the second request. The second request is removed from queue and prefetching
|
||||||
|
// for this request never occurs.
|
||||||
|
verify(mMockClientCallback1, times(1))
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
|
||||||
|
eq(mMockClient1InteractionId - 1));
|
||||||
|
List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue();
|
||||||
|
assertEquals(1, prefetchedNodes.size());
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like above, but tests a series of controller requests from different services to interrupt
|
||||||
|
* prefetching and satisfy a pending node request.
|
||||||
|
*
|
||||||
|
* @throws RemoteException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg()
|
||||||
|
throws RemoteException {
|
||||||
|
mSendClient2RequestForTextAfterTextPrefetched = true;
|
||||||
|
mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
|
||||||
|
final long nodeId = AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mTextView1.getAccessibilityViewId(),
|
||||||
|
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||||
|
|
||||||
|
if (mSendClient2RequestForTextAfterTextPrefetched) {
|
||||||
|
mSendClient2RequestForTextAfterTextPrefetched = false;
|
||||||
|
// TextView1 is prefetched here. Now enqueue client 2's request for
|
||||||
|
// TextView1
|
||||||
|
sendNodeRequestToController(nodeId, mMockClientCallback2,
|
||||||
|
mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Client 1 requests FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// Verify client 1 gets FrameLayout
|
||||||
|
verify(mMockClientCallback1, times(1))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION,
|
||||||
|
mFindInfoCaptor.getValue().getContentDescription());
|
||||||
|
|
||||||
|
// Verify client 1 doesn't have prefetched nodes
|
||||||
|
verify(mMockClientCallback1, never())
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
|
||||||
|
eq(mMockClient1InteractionId));
|
||||||
|
|
||||||
|
// Verify client 2 gets TextView1
|
||||||
|
verify(mMockClientCallback2, times(1))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt());
|
||||||
|
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription());
|
||||||
|
|
||||||
|
// The second request was removed from queue and prefetching for this client request never
|
||||||
|
// occurred as it was satisfied.
|
||||||
|
verify(mMockClientCallback2, never())
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest()
|
||||||
|
throws RemoteException {
|
||||||
|
mSendRequestForTextAndIncludeUnImportantViews = true;
|
||||||
|
mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
|
||||||
|
final long nodeId = AccessibilityNodeInfo.makeNodeId(
|
||||||
|
mTextView1.getAccessibilityViewId(),
|
||||||
|
AccessibilityNodeProvider.HOST_VIEW_ID);
|
||||||
|
|
||||||
|
if (mSendRequestForTextAndIncludeUnImportantViews) {
|
||||||
|
mSendRequestForTextAndIncludeUnImportantViews = false;
|
||||||
|
// TextView1 is prefetched here for client 1. Now enqueue a request from a
|
||||||
|
// different client that holds different fetch flags for TextView1
|
||||||
|
sendNodeRequestToController(nodeId, mMockClientCallback2,
|
||||||
|
mMockClient2InteractionId,
|
||||||
|
FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mockito does not make copies of objects when called. It holds references, so
|
||||||
|
// the captor would point to client 2's results after all requests are processed. Verify
|
||||||
|
// prefetched node immediately
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0);
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription());
|
||||||
|
return null;
|
||||||
|
}).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(),
|
||||||
|
eq(mMockClient1InteractionId));
|
||||||
|
|
||||||
|
// Client 1 requests FrameLayout
|
||||||
|
sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1,
|
||||||
|
mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS);
|
||||||
|
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// Verify client 1 gets FrameLayout
|
||||||
|
verify(mMockClientCallback1, times(1))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
|
||||||
|
eq(mMockClient1InteractionId));
|
||||||
|
|
||||||
|
assertEquals(FRAME_LAYOUT_DESCRIPTION,
|
||||||
|
mFindInfoCaptor.getValue().getContentDescription());
|
||||||
|
|
||||||
|
// Verify client 1 has prefetched results. The only prefetched node is TextView1
|
||||||
|
// (from above doAnswer)
|
||||||
|
verify(mMockClientCallback1, times(1))
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
|
||||||
|
eq(mMockClient1InteractionId));
|
||||||
|
|
||||||
|
// Verify client 2 gets TextView1
|
||||||
|
verify(mMockClientCallback2, times(1))
|
||||||
|
.setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(),
|
||||||
|
eq(mMockClient2InteractionId));
|
||||||
|
assertEquals(TEXT_VIEW_1_DESCRIPTION,
|
||||||
|
mFindInfoCaptor.getValue().getContentDescription());
|
||||||
|
// Verify client 2 has TextView2 as a prefetched node
|
||||||
|
verify(mMockClientCallback2, times(1))
|
||||||
|
.setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(),
|
||||||
|
eq(mMockClient2InteractionId));
|
||||||
|
List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue();
|
||||||
|
assertEquals(1, prefetchedNode.size());
|
||||||
|
assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNodeRequestToController(long requestedNodeId,
|
||||||
|
IAccessibilityInteractionConnectionCallback callback, int interactionId,
|
||||||
|
int prefetchFlags) {
|
||||||
|
final int processAndThreadId = callback == mMockClientCallback1
|
||||||
|
? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID
|
||||||
|
: MOCK_CLIENT_2_THREAD_AND_PROCESS_ID;
|
||||||
|
|
||||||
|
mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread(
|
||||||
|
requestedNodeId,
|
||||||
|
null, interactionId,
|
||||||
|
callback, prefetchFlags,
|
||||||
|
processAndThreadId,
|
||||||
|
processAndThreadId, null, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFrameLayout extends FrameLayout {
|
||||||
|
|
||||||
|
TestFrameLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowVisibility() {
|
||||||
|
// We aren't attached to a window so let's pretend
|
||||||
|
return VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShown() {
|
||||||
|
// Controller check
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAccessibilityViewId() {
|
||||||
|
// static id doesn't reset after tests so return the same one
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addChildrenForAccessibility(ArrayList<View> outChildren) {
|
||||||
|
// ViewGroup#addChildrenForAccessbility sorting logic will switch these two
|
||||||
|
outChildren.add(mTextView1);
|
||||||
|
outChildren.add(mTextView2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeForAccessibility() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(info);
|
||||||
|
info.setContentDescription(FRAME_LAYOUT_DESCRIPTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTextView extends TextView {
|
||||||
|
TestTextView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowVisibility() {
|
||||||
|
return VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShown() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAccessibilityViewId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeForAccessibility() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_1_DESCRIPTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTextView2 extends TextView {
|
||||||
|
TestTextView2(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowVisibility() {
|
||||||
|
return VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShown() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAccessibilityViewId() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeForAccessibility() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(info);
|
||||||
|
info.setContentDescription(TEXT_VIEW_2_DESCRIPTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user