Merge "Prefetching can be interupted by other service requests."
This commit is contained in:
@@ -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> {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user