Java Code Examples for androidx.core.view.accessibility.AccessibilityNodeInfoCompat#refresh()
The following examples show how to use
androidx.core.view.accessibility.AccessibilityNodeInfoCompat#refresh() .
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: FocusProcessorForLogicalNavigation.java From talkback with Apache License 2.0 | 6 votes |
/** * Returns the pivot node for the given navigation action. * * <p>Pivot node is the {@link AccessibilityNodeInfoCompat} based on which we search for target * focusable node. We apply the following strategy to find pivot node: * * <ol> * <li>Use the accessibility focused node if it's non-null and refreshable. * <li>Otherwise use input focused node if {@link NavigationAction#useInputFocusAsPivotIfEmpty} * is set to {@code true} and it's refreshable. * <li>Otherwise return the root node of active window. * </ol> * * <p><strong>Note:</strong> Caller is responsible to recycle the node. */ private AccessibilityNodeInfoCompat getPivotNodeForNavigationAction( NavigationAction navigationAction) { AccessibilityNodeInfoCompat pivot = accessibilityFocusMonitor.getAccessibilityFocus( navigationAction.useInputFocusAsPivotIfEmpty); // If we cannot find a pivot, or the pivot is not accessible, choose the root node if the // active window. if (pivot == null || !pivot.refresh()) { AccessibilityNodeInfoUtils.recycleNodes(pivot); // TODO: We might need to define our own "active window" in TalkBack side. pivot = AccessibilityServiceCompatUtils.getRootInActiveWindow(service); } return pivot; }
Example 2
Source File: FocusProcessorForScreenStateChange.java From talkback with Apache License 2.0 | 6 votes |
/** * Returns the last focused node in {@code window} if it's still valid on screen, otherwise * returns focusable node with the same position. * * <p><strong>Note:</strong> Caller is responsible for recycling the returned node. */ private static AccessibilityNodeInfoCompat getNodeToRestoreFocus( AccessibilityNodeInfoCompat root, FocusActionRecord focusActionRecord) { AccessibilityNodeInfoCompat lastFocusedNode = focusActionRecord.getFocusedNode(); if (lastFocusedNode.refresh()) { return lastFocusedNode; } AccessibilityNodeInfoUtils.recycleNodes(lastFocusedNode); if (root == null) { return null; } AccessibilityNodeInfoCompat nodeAtSamePosition = NodePathDescription.findNode(root, focusActionRecord.getNodePathDescription()); if ((nodeAtSamePosition != null) && AccessibilityNodeInfoUtils.shouldFocusNode(nodeAtSamePosition)) { return nodeAtSamePosition; } AccessibilityNodeInfoUtils.recycleNodes(nodeAtSamePosition); return null; }
Example 3
Source File: ScrollFeedbackManager.java From talkback with Apache License 2.0 | 5 votes |
private static CharSequence getSelectedPageTitle(AccessibilityNodeInfo node) { // We need to refresh() after the scroll to get an accurate page title if (node == null) { return null; } AccessibilityNodeInfoCompat nodeCompat = AccessibilityNodeInfoUtils.toCompat(node); nodeCompat.refresh(); int numChildren = nodeCompat.getChildCount(); // Not the number of pages! CharSequence title = null; for (int i = 0; i < numChildren; ++i) { AccessibilityNodeInfoCompat child = nodeCompat.getChild(i); if (child != null) { try { if (child.isVisibleToUser()) { if (title == null) { // Try to roughly match RulePagerPage, which uses getNodeText // (but completely matching all the time is not critical). title = AccessibilityNodeInfoUtils.getNodeText(child); } else { // Multiple visible children, abort. return null; } } } finally { child.recycle(); } } } return title; }
Example 4
Source File: AccessibilityNodeInfoUtils.java From talkback with Apache License 2.0 | 5 votes |
/** * Returns a fresh copy of {@code node} with properties that are less likely to be stale. Returns * {@code null} if the node can't be found anymore. */ public static AccessibilityNodeInfoCompat refreshNode(AccessibilityNodeInfoCompat node) { if (node == null) { return null; } AccessibilityNodeInfoCompat nodeCopy = AccessibilityNodeInfoCompat.obtain(node); if (nodeCopy.refresh()) { return nodeCopy; } else { nodeCopy.recycle(); return null; } }
Example 5
Source File: TextEditingUtils.java From talkback with Apache License 2.0 | 5 votes |
/** * Set the text and the cursor position of the given {@link AccessibilityNodeInfoCompat}. * * @param nodeCompat The {@link AccessibilityNodeInfoCompat} for which to set text * @param arguments The {@link Bundle} containing the text to set and the selection start and end * indices to set * @return {@code true} if setting the text and the cursor position is successful */ public static boolean setText(AccessibilityNodeInfoCompat nodeCompat, Bundle arguments) { if (!PerformActionUtils.performAction( nodeCompat, AccessibilityNodeInfo.ACTION_SET_TEXT, arguments, null /* EventId */)) { return false; } // Restore the cursor position. If arguments has no ACTION_ARGUMENT_SELECTION_START_INT, this // moves the cursor to the beginning of the text. nodeCompat.refresh(); return selectText( nodeCompat, arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT), arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT)); }
Example 6
Source File: FocusActor.java From talkback with Apache License 2.0 | 5 votes |
/** Restore focus with the node cached before context menu or dialog appeared. */ public boolean restoreFocus(EventId eventId) { AccessibilityNodeInfoCompat nodeToRestoreFocus = history.popCachedNodeToRestoreFocus(); if (nodeToRestoreFocus == null) { return false; } try { if (!nodeToRestoreFocus.refresh() || !nodeToRestoreFocus.isVisibleToUser()) { return false; } return AccessibilityNodeInfoUtils.isInWindow( nodeToRestoreFocus, nodeToRestoreFocus.getWindow()) && focusManagerInternal.setAccessibilityFocus( nodeToRestoreFocus, /* forceRefocusIfAlreadyFocused= */ false, FocusActionInfo.builder() .setSourceAction(FocusActionInfo.SCREEN_STATE_CHANGE) .setInitialFocusType(FocusActionInfo.RESTORED_LAST_FOCUS) .build(), eventId); } finally { AccessibilityNodeInfoUtils.recycleNodes(nodeToRestoreFocus); } }
Example 7
Source File: AccessibilityFocusManager.java From talkback with Apache License 2.0 | 5 votes |
/** Called by InputFocusInterpreter. */ @Override public boolean onViewTargeted( AccessibilityNodeInfoCompat targetedNode, boolean isInputFocus, EventId eventId) { LogUtils.d(TAG, "View targeted: IsInputFocus=%s; Node=%s", isInputFocus, targetedNode); FocusActionInfo focusActionInfo = FocusActionInfo.builder().setSourceAction(FocusActionInfo.FOCUS_SYNCHRONIZATION).build(); return targetedNode.refresh() && pipeline.returnFeedback(eventId, Feedback.focus(targetedNode, focusActionInfo)); }
Example 8
Source File: DirectionalTraversalStrategy.java From talkback with Apache License 2.0 | 4 votes |
/** * Goes through root and its descendant nodes, sorting out the focusable nodes and the container * nodes for use in finding focus. * * @return whether the root is focusable or has focusable children in its hierarchy */ private boolean processNodes(AccessibilityNodeInfoCompat root, boolean forceRefresh) { if (root == null) { return false; } if (forceRefresh) { root.refresh(); } Rect currentRect = new Rect(); root.getBoundsInScreen(currentRect); // Determine if the node is inside mRootRect (within a fudge factor). If it is outside, we // will optimize by skipping its entire hierarchy. if (!Rect.intersects(currentRect, mRootRectPadded)) { return false; } AccessibilityNodeInfoCompat rootNode = AccessibilityNodeInfoCompat.obtain(root); mAllNodes.add(rootNode); // When we reach a node that supports web navigation, we traverse using the web navigation // actions, so we should not add any of its descendants to the list of focusable nodes. if (WebInterfaceUtils.hasNativeWebContent(rootNode)) { mFocusables.add(rootNode); return true; } else { boolean isFocusable = AccessibilityNodeInfoUtils.shouldFocusNode(rootNode, mSpeakingNodesCache); if (isFocusable) { mFocusables.add(rootNode); } boolean hasFocusableDescendants = false; int childCount = rootNode.getChildCount(); for (int i = 0; i < childCount; ++i) { AccessibilityNodeInfoCompat child = rootNode.getChild(i); if (child != null) { hasFocusableDescendants |= processNodes(child, forceRefresh); child.recycle(); } } if (hasFocusableDescendants) { mContainers.add(rootNode); } return isFocusable || hasFocusableDescendants; } }
Example 9
Source File: TextEditingUtils.java From talkback with Apache License 2.0 | 4 votes |
/** * Deletes text in the given {@link AccessibilityNodeInfoCompat} with the granularity indicated in * the given {@link Bundle}. * * @param nodeCompat The {@link AccessibilityNodeInfo} containing the text to delete * @param arguments The {@link Bundle} containing the granularity arguments for deletion * @return {@code true} if the deletion is successful */ public static boolean deleteTextWithGranularity( AccessibilityNodeInfoCompat nodeCompat, Bundle arguments) { if (arguments == Bundle.EMPTY) { return false; } nodeCompat.refresh(); CharSequence text = nodeCompat.getText(); // Find the bounds of the section of text to delete. int deleteSectionEnd = nodeCompat.getTextSelectionEnd(); int deleteSectionStart; int deleteGranularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); if (deleteGranularity == ACTION_GRANULARITY_SENTENCE) { deleteSectionStart = getEndOfLastSentenceBeforeIndex(text, deleteSectionEnd); } else if (deleteGranularity == ACTION_GRANULARITY_HIGHLIGHT) { deleteSectionStart = nodeCompat.getTextSelectionStart(); } else { if (!PerformActionUtils.performAction( nodeCompat, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments, null)) { return false; } nodeCompat.refresh(); deleteSectionStart = nodeCompat.getTextSelectionEnd(); } int deleteSectionLowerIndex = Math.min(deleteSectionStart, deleteSectionEnd); int deleteSectionUpperIndex = Math.max(deleteSectionStart, deleteSectionEnd); // Set text to be the entire existing text minus the section to delete. String oldText = (text == null) ? "" : text.toString(); String newText = oldText.substring(0, deleteSectionLowerIndex) + oldText.substring(deleteSectionUpperIndex); arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, newText); if (!PerformActionUtils.performAction( nodeCompat, AccessibilityNodeInfo.ACTION_SET_TEXT, arguments, null)) { return false; } nodeCompat.refresh(); // Place the cursor back where it was before the deletion. int endOfText = (text == null) ? 0 : text.length(); int newCursorPosition = Math.min(deleteSectionLowerIndex, endOfText); return selectText(nodeCompat, newCursorPosition, newCursorPosition); }
Example 10
Source File: TextEditingUtils.java From talkback with Apache License 2.0 | 4 votes |
/** * Select the text in the given {@link AccessibilityNodeInfoCompat} with the granularity indicated * in the given {@link Bundle}. * * @param nodeCompat The {@link AccessibilityNodeInfoCompat} to select text in * @param arguments The {@link Bundle} containing either (a) the granularity argument for * selection or (b) the explicit start and end selection indices * @return {@code true} if the selection is successful */ public static boolean selectTextWithGranularity( AccessibilityNodeInfoCompat nodeCompat, Bundle arguments) { if (arguments == Bundle.EMPTY) { return false; } nodeCompat.refresh(); CharSequence text = nodeCompat.getText(); final int noGranularityPresent = -1; switch (arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, noGranularityPresent)) { case ACTION_GRANULARITY_ALL: return selectText(nodeCompat, 0, text.length()); case ACTION_GRANULARITY_SENTENCE: return extendSelectionBackOneSentence(nodeCompat); case noGranularityPresent: // Select text based on the explicit selection start and end boundaries. int noSelectionIndexPresent = -1; int selectionStart = arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, noSelectionIndexPresent); int selectionEnd = arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, noSelectionIndexPresent); return (selectionStart != noSelectionIndexPresent) && (selectionEnd != noSelectionIndexPresent) && selectText(nodeCompat, selectionStart, selectionEnd); default: int currentSelectionEnd = Math.max(nodeCompat.getTextSelectionEnd(), nodeCompat.getTextSelectionStart()); // If text is already selected, extend the selection. To do this, first move the cursor to // the beginning of the current selection. (When text is selected, and a previous movement // action is performed, the cursor moves by granularity from the end of the current // selection.) if (isTextSelected(nodeCompat) && !selectText( nodeCompat, nodeCompat.getTextSelectionStart(), nodeCompat.getTextSelectionStart())) { return false; } nodeCompat.refresh(); if (!PerformActionUtils.performAction( nodeCompat, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, arguments, null)) { return false; } nodeCompat.refresh(); return selectText(nodeCompat, nodeCompat.getTextSelectionEnd(), currentSelectionEnd); } }
Example 11
Source File: AccessibilityFocusMonitor.java From talkback with Apache License 2.0 | 4 votes |
/** * Returns accessibility focused node if it's visible on screen. Otherwise returns input focused * edit field if{@code returnInputFocusedEditFieldIfNullOrInvisible} is set to {@code true}. * Called by DirectionNavigationController. * * <p><strong>Note:</strong> * * <ul> * <li>The client is responsible for recycling the returned node. * <li>The returned node might not pass {@link * AccessibilityNodeInfoUtils#shouldFocusNode(AccessibilityNodeInfoCompat)}, the caller * should validate the result if needed. * </ul> */ public @Nullable AccessibilityNodeInfoCompat getAccessibilityFocus(boolean useInputFocusIfEmpty) { // Nodes to be recycled. AccessibilityNodeInfoCompat root = null; AccessibilityNodeInfoCompat a11yFocusedNode = null; AccessibilityNodeInfoCompat inputFocusedNode = null; AccessibilityNodeInfoCompat lastFocusedEditFieldInHistory = null; try { // First, see if we've already placed accessibility focus. root = AccessibilityServiceCompatUtils.getRootInAccessibilityFocusedWindow(service); if (root == null) { return null; } a11yFocusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); if ((a11yFocusedNode != null) && AccessibilityNodeInfoUtils.isVisible(a11yFocusedNode)) { return AccessibilityNodeInfoUtils.obtain(a11yFocusedNode); } if (!useInputFocusIfEmpty) { return null; } // TODO: If there's no focused node, we should either mimic following // focus from new window or try to be smart for things like list views. inputFocusedNode = AccessibilityServiceCompatUtils.getInputFocusedNode(service); if ((inputFocusedNode != null) && inputFocusedNode.isFocused() && (inputFocusedNode.isEditable() || (Role.getRole(inputFocusedNode) == Role.ROLE_EDIT_TEXT))) { return AccessibilityNodeInfoUtils.obtain(inputFocusedNode); } // If we can't find the focused node but the keyboard is showing, return the last editable. // This will occur if the input-focused view is actually a virtual view (e.g. in WebViews). // Note: need to refresh() in order to verify that the node is still available on-screen. FocusActionRecord record = history.getLastEditableFocusActionRecord(); lastFocusedEditFieldInHistory = (record == null) ? null : record.getFocusedNode(); if ((lastFocusedEditFieldInHistory != null) && lastFocusedEditFieldInHistory.refresh()) { // TODO: Shall we check the presence of IME window? // IME window check below is copied from legacy CursorController. What if the device is // connected to bluetooth keyboard? WindowManager windowManager = new WindowManager(false); // RTL state doesn't matter. windowManager.setWindows(AccessibilityServiceCompatUtils.getWindows(service)); if (windowManager.isInputWindowOnScreen()) { return AccessibilityNodeInfoCompat.obtain(lastFocusedEditFieldInHistory); } } return null; } finally { AccessibilityNodeInfoUtils.recycleNodes( root, a11yFocusedNode, inputFocusedNode, lastFocusedEditFieldInHistory); } }
Example 12
Source File: FocusProcessorForLogicalNavigation.java From talkback with Apache License 2.0 | 4 votes |
/** * Finds next node which matches search-filter. Optionally focuses matching node. Returns matching * node, which caller must recycle. */ private @Nullable AccessibilityNode search( boolean startAtRoot, boolean focus, Filter<AccessibilityNodeInfoCompat> filter) { AccessibilityNodeInfoCompat start = null; AccessibilityNodeInfoCompat rootNode = null; AccessibilityNodeInfoCompat target = null; TraversalStrategy traversalStrategy = null; try { // Try to find current accessibility-focused node. if (!startAtRoot) { start = accessibilityFocusMonitor.getAccessibilityFocus(/* useInputFocusIfEmpty= */ false); } // Find root node, or return failure. if (start == null || !start.refresh()) { // Start from root node of active window. AccessibilityNodeInfoUtils.recycleNodes(start); rootNode = AccessibilityServiceCompatUtils.getRootInActiveWindow(service); if (rootNode == null) { return null; } start = AccessibilityNodeInfoCompat.obtain(rootNode); // Copy, to not double-recycle root. } else { // Derive root node from start node. rootNode = AccessibilityNodeInfoUtils.getRoot(start); if (rootNode == null) { return null; } } // Search forward for node satisfying filter. @SearchDirection int direction = TraversalStrategy.SEARCH_FOCUS_FORWARD; traversalStrategy = TraversalStrategyUtils.getTraversalStrategy(rootNode, direction); target = TraversalStrategyUtils.searchFocus(traversalStrategy, start, direction, filter); if (target == null) { return null; } // Focus first matching node. // Focus is implemented in the same function as searching, because they use the same rootNode // and traversalStrategy. if (focus) { EventId eventId = EVENT_ID_UNTRACKED; ensureOnScreen(target, /* shouldScroll= */ false, direction, traversalStrategy, eventId); NavigationAction navigationAction = new NavigationAction.Builder() .setAction(NavigationAction.DIRECTIONAL_NAVIGATION) .setDirection(direction) .build(); boolean focused = setAccessibilityFocusInternal(target, navigationAction, eventId); if (!focused) { return null; } } // Null target so that it is not recycled, then return target. AccessibilityNode targetNode = AccessibilityNode.takeOwnership(target); target = null; return targetNode; } finally { AccessibilityNodeInfoUtils.recycleNodes(start, rootNode, target); TraversalStrategyUtils.recycle(traversalStrategy); } }
Example 13
Source File: FocusProcessorForLogicalNavigation.java From talkback with Apache License 2.0 | 4 votes |
/** * Called when we receive result event for auto-scroll action with default target. * * <p><b>Warning:</b> Do not rely too much logic on {@code focusBeforeScroll}. It is possible in * RecyclerView when {@code focusBeforeScroll} goes off screen, and after calling {@link * AccessibilityNodeInfoCompat#refresh()}, the node is reused and pointed to another emerging list * item. This is how RecyclerView "recycles" views and we cannot get rid of it. If using this * field, please refresh and validate the node to ensure it is identical with what it was before * scroll action. */ private void handleViewAutoScrolledForDirectionalNavigationWithDefaultTarget( AccessibilityNodeInfoCompat scrolledNode, AccessibilityNodeInfoCompat focusBeforeScroll, NavigationAction sourceAction, EventId eventId) { // Nodes to be recycled. AccessibilityNodeInfoCompat nodeToFocus = null; // Local TraversalStrategy generated in sub-tree of scrolledNode. TraversalStrategy localTraversalStrategy = null; try { localTraversalStrategy = TraversalStrategyUtils.getTraversalStrategy(scrolledNode, sourceAction.searchDirection); Filter<AccessibilityNodeInfoCompat> nodeFilter = NavigationTarget.createNodeFilter( sourceAction.targetType, localTraversalStrategy.getSpeakingNodesCache()); boolean validAccessibilityFocus = focusBeforeScroll.refresh() && AccessibilityNodeInfoUtils.isVisible(focusBeforeScroll); NavigationAction navigationAction = NavigationAction.Builder.copy(sourceAction) .setAutoScrollAttempt(sourceAction.autoScrollAttempt + 1) .build(); if (validAccessibilityFocus) { // Try to find the next focusable node based on current focus. nodeToFocus = TraversalStrategyUtils.searchFocus( localTraversalStrategy, focusBeforeScroll, sourceAction.searchDirection, nodeFilter); if (nodeToFocus == null) { // If no more item is exposed, repeat navigation action. onDirectionalNavigationAction( /* pivot= */ focusBeforeScroll, /* ignoreDescendantsOfPivot= */ false, navigationAction, eventId); return; } } else { // Try to find the next focusable node based on current focus. nodeToFocus = TraversalStrategyUtils.searchFocus( localTraversalStrategy, focusBeforeScroll, sourceAction.searchDirection, nodeFilter); // Fallback solution: Use the first/last item under scrollable node as the target. if (nodeToFocus == null) { nodeToFocus = TraversalStrategyUtils.findInitialFocusInNodeTree( localTraversalStrategy, scrolledNode, sourceAction.searchDirection, nodeFilter); } if (nodeToFocus == null) { // Since there is no visible/valid accessibility focus on screen, we play safe and don't // repeat navigation action without a valid pivot node. return; } } // If we're moving backward with default target from native views to WebView container node, // automatically descend to the last element in the WebView. int logicalDirection = TraversalStrategyUtils.getLogicalDirection( navigationAction.searchDirection, WindowManager.isScreenLayoutRTL(service)); if (logicalDirection == TraversalStrategy.SEARCH_FOCUS_BACKWARD) { // We don't need to check role of the last focused node, because auto-scroll always // happens on native pivot. if (Role.getRole(nodeToFocus) == Role.ROLE_WEB_VIEW) { if (navigateToHtmlTarget(/* pivot= */ nodeToFocus, navigationAction, eventId)) { return; } } } setAccessibilityFocusInternal(nodeToFocus, navigationAction, eventId); } finally { AccessibilityNodeInfoUtils.recycleNodes(nodeToFocus); TraversalStrategyUtils.recycle(localTraversalStrategy); } }
Example 14
Source File: FocusProcessorForManualScroll.java From talkback with Apache License 2.0 | 4 votes |
public boolean onNodeManuallyScrolled( AccessibilityNodeInfoCompat scrolledNode, @TraversalStrategy.SearchDirection int direction, EventId eventId) { // Nodes to be recycled. AccessibilityNodeInfoCompat currentA11yFocusedNode = null; AccessibilityNodeInfoCompat lastA11yFocusedNode = null; AccessibilityNodeInfoCompat nodeToFocus = null; TraversalStrategy traversalStrategy = null; try { currentA11yFocusedNode = accessibilityFocusMonitor.getAccessibilityFocus(/* useInputFocusIfEmpty= */ false); if (AccessibilityNodeInfoUtils.shouldFocusNode(currentA11yFocusedNode)) { return false; } NodePathDescription lastFocusNodePathDescription = actorState.getFocusHistory().getLastFocusNodePathDescription(); if (lastFocusNodePathDescription == null) { return false; } // We use attributes from AccessibilityNodeInfo, including viewIdResourceName to match // ancestor node. However, on pre-OMR1 devices, source node of TYPE_VIEW_SCROLLED events // always have null viewIdResourceName unless we call refresh(). if (!BuildVersionUtils.isAtLeastOMR1()) { scrolledNode.refresh(); } if (!lastFocusNodePathDescription.containsNodeByHashAndIdentity(scrolledNode)) { return false; } // Try to focus on the next/previous focusable node. traversalStrategy = TraversalStrategyUtils.getTraversalStrategy(scrolledNode, direction); final Map<AccessibilityNodeInfoCompat, Boolean> speakingNodeCache = traversalStrategy.getSpeakingNodesCache(); Filter<AccessibilityNodeInfoCompat> nodeFilter = new Filter<AccessibilityNodeInfoCompat>() { @Override public boolean accept(AccessibilityNodeInfoCompat obj) { return AccessibilityNodeInfoUtils.shouldFocusNode(obj, speakingNodeCache); } }; nodeToFocus = TraversalStrategyUtils.findInitialFocusInNodeTree( traversalStrategy, scrolledNode, direction, nodeFilter); if (nodeToFocus == null) { return false; } FocusActionInfo focusActionInfo = new FocusActionInfo.Builder().setSourceAction(FocusActionInfo.MANUAL_SCROLL).build(); return pipeline.returnFeedback(eventId, Feedback.focus(nodeToFocus, focusActionInfo)); } finally { AccessibilityNodeInfoUtils.recycleNodes( currentA11yFocusedNode, lastA11yFocusedNode, nodeToFocus); TraversalStrategyUtils.recycle(traversalStrategy); } }