Java Code Examples for androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getChildCount()

The following examples show how to use androidx.core.view.accessibility.AccessibilityNodeInfoCompat#getChildCount() . 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: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 6 votes vote down vote up
public static int countVisibleChildren(AccessibilityNodeInfoCompat node) {
  if (node == null) {
    return 0;
  }
  int childCount = node.getChildCount();
  int childVisibleCount = 0;
  for (int i = 0; i < childCount; ++i) {
    AccessibilityNodeInfoCompat child = node.getChild(i);
    if (child != null) {
      try {
        if (child.isVisibleToUser()) {
          ++childVisibleCount;
        }
      } finally {
        child.recycle();
      }
    }
  }
  return childVisibleCount;
}
 
Example 2
Source File: NodeDescription.java    From talkback with Apache License 2.0 6 votes vote down vote up
public boolean indexMatches(AccessibilityNodeInfoCompat node) {
  if (node == null) {
    return false;
  }
  if (indexType == INDEX_TYPE_COLLECTION) {
    CollectionItemInfoCompat itemInfo = node.getCollectionItemInfo();
    return (itemInfo != null)
        && (itemInfo.getRowIndex() == rowIndex)
        && (itemInfo.getColumnIndex() == columnIndex);
  } else {
    AccessibilityNodeInfoCompat parent = null;
    AccessibilityNodeInfoCompat child = null;
    try {
      parent = node.getParent();
      if (parent == null || parent.getChildCount() <= rawIndexInParent) {
        return false;
      }
      child = parent.getChild(rawIndexInParent);
      return node.equals(child);

    } finally {
      AccessibilityNodeInfoUtils.recycleNodes(parent, child);
    }
  }
}
 
Example 3
Source File: NodePathDescription.java    From talkback with Apache License 2.0 6 votes vote down vote up
/** <strong>Note:</strong> Caller is responsible to recycle the returned node. */
private static AccessibilityNodeInfoCompat findChildMatchesIndex(
    AccessibilityNodeInfoCompat parent, NodeDescription childDescription) {
  if (childDescription.indexType == NodeDescription.INDEX_TYPE_RAW) {
    if ((childDescription.rawIndexInParent == NodeDescription.UNDEFINED_INDEX)
        || (parent.getChildCount() <= childDescription.rawIndexInParent)) {
      return null;
    } else {
      return parent.getChild(childDescription.rawIndexInParent);
    }
  } else {
    // INDEX_TYPE_COLLECTION
    for (int i = 0; i < parent.getChildCount(); i++) {
      AccessibilityNodeInfoCompat child = parent.getChild(i);
      // Validate CollectionItemInfo.
      if (childDescription.indexMatches(child)) {
        return child;
      }
      AccessibilityNodeInfoUtils.recycleNodes(child);
    }
    return null;
  }
}
 
Example 4
Source File: TreeDebug.java    From talkback with Apache License 2.0 6 votes vote down vote up
private static void logNodeTree(
    AccessibilityNodeInfoCompat node, String indent, HashSet<AccessibilityNodeInfoCompat> seen) {
  if (!seen.add(node)) {
    LogUtils.v(TAG, "Cycle: %d", node.hashCode());
    return;
  }

  // Include the hash code as a "poor man's" id, knowing that it
  // might not always be unique.
  LogUtils.v(TAG, "%s(%d)%s", indent, node.hashCode(), nodeDebugDescription(node));

  indent += "  ";
  int childCount = node.getChildCount();
  for (int i = 0; i < childCount; ++i) {
    AccessibilityNodeInfoCompat child = node.getChild(i);
    if (child == null) {
      LogUtils.v(TAG, "%sCouldn't get child %d", indent, i);
      continue;
    }

    logNodeTree(child, indent, seen);
  }
}
 
Example 5
Source File: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 6 votes vote down vote up
private static boolean hasVisibleChildren(AccessibilityNodeInfoCompat node) {
  int childCount = node.getChildCount();
  for (int i = 0; i < childCount; ++i) {
    AccessibilityNodeInfoCompat child = node.getChild(i);
    if (child != null) {
      try {
        if (child.isVisibleToUser()) {
          return true;
        }
      } finally {
        child.recycle();
      }
    }
  }

  return false;
}
 
Example 6
Source File: CollectionState.java    From talkback with Apache License 2.0 6 votes vote down vote up
private static boolean shouldEnter(@NonNull AccessibilityNodeInfoCompat collectionRoot) {
  if (collectionRoot.getCollectionInfo() != null) {
    CollectionInfoCompat collectionInfo = collectionRoot.getCollectionInfo();
    if (!hasMultipleItems(
        collectionInfo.getRowCount(),
        collectionInfo.getColumnCount(),
        Role.getRole(collectionRoot))) {
      return false;
    }
  } else if (collectionRoot.getChildCount() <= 1) {
    // If we don't have collection info, use the child count as an approximation.
    return false;
  }

  // If the collection is flat and contains other flat collections, then we discard it.
  // We only announce hierarchies of collections if they are explicitly marked hierarchical.
  // Otherwise we announce only the innermost collection.
  if (FILTER_FLAT_COLLECTION.accept(collectionRoot)
      && AccessibilityNodeInfoUtils.hasMatchingDescendant(
          collectionRoot, FILTER_FLAT_COLLECTION)) {
    return false;
  }

  return true;
}
 
Example 7
Source File: CollectionState.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * For finding the name of the header, we want to use a simpler strategy than the
 * NodeSpeechRuleProcessor. We don't want to include the role description of items within the
 * header, because it will add confusion when the header name is appended to collection items. But
 * we do want to search down the tree in case the immediate root element doesn't have text.
 *
 * <p>We traverse single children of single children until we find a node with text. If we hit any
 * node that has multiple children, we simply stop the search and return {@code null}.
 */
public static @Nullable CharSequence getHeaderText(AccessibilityNodeInfoCompat node) {
  if (node == null) {
    return null;
  }

  Set<AccessibilityNodeInfoCompat> visitedNodes = new HashSet<>();
  try {
    AccessibilityNodeInfoCompat currentNode = AccessibilityNodeInfoCompat.obtain(node);
    while (currentNode != null) {
      if (!visitedNodes.add(currentNode)) {
        // Cycle in traversal.
        currentNode.recycle();
        return null;
      }

      CharSequence nodeText = AccessibilityNodeInfoUtils.getNodeText(currentNode);
      if (nodeText != null) {
        return nodeText;
      }

      if (currentNode.getChildCount() != 1) {
        return null;
      }

      currentNode = currentNode.getChild(0);
    }
  } finally {
    AccessibilityNodeInfoUtils.recycleNodes(visitedNodes);
  }

  return null;
}
 
Example 8
Source File: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 5 votes vote down vote up
public static @Nullable CharSequence getSelectedPageTitle(AccessibilityNodeInfoCompat viewPager) {
  if ((viewPager == null) || (Role.getRole(viewPager) != Role.ROLE_PAGER)) {
    return null;
  }

  int numChildren = viewPager.getChildCount(); // Not the number of pages!
  CharSequence title = null;
  for (int i = 0; i < numChildren; ++i) {
    AccessibilityNodeInfoCompat child = viewPager.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 = getNodeText(child);
          } else {
            // Multiple visible children, abort.
            return null;
          }
        }
      } finally {
        recycleNodes(child);
      }
    }
  }

  return title;
}
 
Example 9
Source File: AccessibilityUtil.java    From screenshot-tests-for-android with Apache License 2.0 5 votes vote down vote up
/**
 * Returns whether the supplied {@link View} and {@link AccessibilityNodeInfoCompat} would produce
 * spoken feedback if it were accessibility focused. NOTE: not all speaking nodes are focusable.
 *
 * @param view The {@link View} to evaluate
 * @param node The {@link AccessibilityNodeInfoCompat} to evaluate
 * @return {@code true} if it meets the criterion for producing spoken feedback
 */
public static boolean isSpeakingNode(
    @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) {
  if (node == null || view == null) {
    return false;
  }

  final int important = ViewCompat.getImportantForAccessibility(view);
  if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      || (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO && node.getChildCount() <= 0)) {
    return false;
  }

  return node.isCheckable() || hasText(node) || hasNonActionableSpeakingDescendants(node, view);
}
 
Example 10
Source File: InputFocusInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Gets target child node from the source AdapterView node.
 *
 * <p><strong>Note:</strong> Caller is responsible for recycling the returned node.
 */
private static @Nullable AccessibilityNodeInfoCompat getTargetChildFromAdapterView(
    AccessibilityEvent event) {
  AccessibilityNodeInfoCompat sourceNode = null;
  try {
    sourceNode = AccessibilityNodeInfoUtils.toCompat(event.getSource());
    if (sourceNode == null) {
      return null;
    }

    if (event.getItemCount() <= 0
        || event.getFromIndex() < 0
        || event.getCurrentItemIndex() < 0) {
      return null;
    }
    int index = event.getCurrentItemIndex() - event.getFromIndex();
    if (index < 0 || index >= sourceNode.getChildCount()) {
      return null;
    }
    AccessibilityNodeInfoCompat targetChildNode = sourceNode.getChild(index);

    // TODO: Think about to replace childNode check with sourceNode check.
    if ((targetChildNode == null)
        || !AccessibilityNodeInfoUtils.isTopLevelScrollItem(targetChildNode)) {
      AccessibilityNodeInfoUtils.recycleNodes(targetChildNode);
      return null;
    } else {
      return targetChildNode;
    }
  } finally {
    AccessibilityNodeInfoUtils.recycleNodes(sourceNode);
  }
}
 
Example 11
Source File: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Collects all descendants that match filter, into matches.
 *
 * @param node The root node to start searching.
 * @param filter The filter to match the nodes against.
 * @param matchRoot Flag that allows match with root node.
 * @param visitedNodes The set of nodes already visited, for protection against loops. This will
 *     be modified. Caller is responsible to recycle the nodes.
 * @param matches The list of nodes matching filter. This will be appended to. Caller is
 *     responsible to recycle this.
 */
private static void getMatchingDescendants(
    @Nullable AccessibilityNodeInfoCompat node,
    Filter<AccessibilityNodeInfoCompat> filter,
    boolean matchRoot,
    Set<AccessibilityNodeInfoCompat> visitedNodes,
    List<AccessibilityNodeInfoCompat> matches) {

  if (node == null) {
    return;
  }

  // Update visited nodes.
  if (visitedNodes.contains(node)) {
    return;
  } else {
    visitedNodes.add(AccessibilityNodeInfoCompat.obtain(node)); // Caller must recycle
  }

  // If node matches filter... collect node.
  if (matchRoot && filter.accept(node)) {
    matches.add(AccessibilityNodeInfoCompat.obtain(node)); // Caller must recycle
  }

  // For each child of node...
  int childCount = node.getChildCount();
  for (int i = 0; i < childCount; ++i) {
    AccessibilityNodeInfoCompat child = node.getChild(i); // Must recycle
    if (child == null) {
      continue;
    }
    try {
      // Recurse on child.
      getMatchingDescendants(child, filter, /* matchRoot= */ true, visitedNodes, matches);
    } finally {
      child.recycle();
    }
  }
}
 
Example 12
Source File: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Returns the result of applying a filter using breadth-first traversal.
 *
 * @param node The root node to traverse from.
 * @param filter The filter to satisfy.
 * @return The first node reached via BFS traversal that satisfies the filter.
 */
public static AccessibilityNodeInfoCompat searchFromBfs(
    AccessibilityNodeInfoCompat node, Filter<AccessibilityNodeInfoCompat> filter) {
  if (node == null) {
    return null;
  }

  final ArrayDeque<AccessibilityNodeInfoCompat> queue = new ArrayDeque<>();
  Set<AccessibilityNodeInfoCompat> visitedNodes = new HashSet<>();

  queue.add(AccessibilityNodeInfoCompat.obtain(node));

  try {
    while (!queue.isEmpty()) {
      final AccessibilityNodeInfoCompat item = queue.removeFirst();
      visitedNodes.add(item);

      if (filter.accept(item)) {
        return item;
      }

      final int childCount = item.getChildCount();

      for (int i = 0; i < childCount; i++) {
        final AccessibilityNodeInfoCompat child = item.getChild(i);

        if (child != null && !visitedNodes.contains(child)) {
          queue.addLast(child);
        }
      }
      item.recycle();
    }
  } finally {
    while (!queue.isEmpty()) {
      queue.removeFirst().recycle();
    }
  }

  return null;
}
 
Example 13
Source File: ScrollFeedbackManager.java    From talkback with Apache License 2.0 5 votes vote down vote up
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 14
Source File: FocusProcessorForLogicalNavigation.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Returns the first or the last child.
 *
 * @param node The parent node whose first or last child is returned.
 * @param firstChild If {@code true} indicates first child, else last child.
 * @return First or last child of the {@code node}
 */
private static AccessibilityNodeInfoCompat getFirstOrLastChild(
    @Nullable AccessibilityNodeInfoCompat node, boolean firstChild) {
  if (node != null && node.getChildCount() > 0) {
    int childNumber = 0;
    if (!firstChild) {
      childNumber = node.getChildCount() - 1;
    }
    return node.getChild(childNumber);
  }
  return null;
}
 
Example 15
Source File: AccessibilityUtil.java    From screenshot-tests-for-android with Apache License 2.0 5 votes vote down vote up
/**
 * Determines if any of the provided {@link View}'s and {@link AccessibilityNodeInfoCompat}'s
 * ancestors can receive accessibility focus
 *
 * @param view The {@link View} to evaluate
 * @param node The {@link AccessibilityNodeInfoCompat} to evaluate
 * @return {@code true} if an ancestor of may receive accessibility focus
 */
public static boolean hasFocusableAncestor(
    @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) {
  if (node == null || view == null) {
    return false;
  }

  final ViewParent parentView = ViewCompat.getParentForAccessibility(view);
  if (!(parentView instanceof View)) {
    return false;
  }

  final AccessibilityNodeInfoCompat parentNode = createNodeInfoFromView((View) parentView);
  try {
    if (parentNode == null) {
      return false;
    }

    if (areBoundsIdenticalToWindow(parentNode, (View) parentView)
        && parentNode.getChildCount() > 0) {
      return false;
    }

    if (isAccessibilityFocusable(parentNode, (View) parentView)) {
      return true;
    }

    if (hasFocusableAncestor(parentNode, (View) parentView)) {
      return true;
    }
  } finally {
    if (parentNode != null) {
      parentNode.recycle();
    }
  }
  return false;
}
 
Example 16
Source File: AccessibilityNodeInfoUtils.java    From talkback with Apache License 2.0 4 votes vote down vote up
private static boolean hasNonActionableSpeakingChildren(
    AccessibilityNodeInfoCompat node,
    Map<AccessibilityNodeInfoCompat, Boolean> speakingNodeCache,
    Set<AccessibilityNodeInfoCompat> visitedNodes) {
  final int childCount = node.getChildCount();

  AccessibilityNodeInfoCompat child;

  // Has non-actionable, speaking children?
  for (int i = 0; i < childCount; i++) {
    child = node.getChild(i);

    if (child == null) {
      LogUtils.v(TAG, "Child %d is null, skipping it", i);
      continue;
    }

    if (!visitedNodes.add(child)) {
      child.recycle();
      return false;
    }

    // Ignore invisible nodes.
    if (!isVisible(child)) {
      LogUtils.v(TAG, "Child %d is invisible, skipping it", i);
      continue;
    }

    // Ignore focusable nodes.
    if (isAccessibilityFocusableInternal(child, speakingNodeCache, visitedNodes)) {
      LogUtils.v(TAG, "Child %d is focusable, skipping it", i);
      continue;
    }

    // Recursively check non-focusable child nodes.
    if (isSpeakingNode(child, speakingNodeCache, visitedNodes)) {
      LogUtils.v(TAG, "Does have actionable speaking children (child %d)", i);
      return true;
    }
  }

  LogUtils.v(TAG, "Does not have non-actionable speaking children");
  return false;
}
 
Example 17
Source File: CollectionState.java    From talkback with Apache License 2.0 4 votes vote down vote up
private static void updateTableHeaderInfo(
    AccessibilityNodeInfoCompat collectionRoot,
    SparseArray<CharSequence> rowHeaders,
    SparseArray<CharSequence> columnHeaders,
    boolean computeHeaders) {
  rowHeaders.clear();
  columnHeaders.clear();

  if (!computeHeaders) {
    return;
  }

  if (collectionRoot == null || collectionRoot.getCollectionInfo() == null) {
    return;
  }

  // Limit search to children and grandchildren of the root node for performance reasons.
  // We want to search grandchildren because web pages put table headers <th> inside table
  // rows <tr> so they are nested two levels down.
  CollectionInfoCompat collectionInfo = collectionRoot.getCollectionInfo();
  int numChildren = collectionRoot.getChildCount();
  for (int i = 0; i < numChildren; ++i) {
    AccessibilityNodeInfoCompat child = collectionRoot.getChild(i);
    if (child == null) {
      continue;
    }
    if (!updateSingleTableHeader(child, collectionInfo, rowHeaders, columnHeaders)) {
      int numGrandchildren = child.getChildCount();
      for (int j = 0; j < numGrandchildren; ++j) {
        AccessibilityNodeInfoCompat grandchild = child.getChild(j);
        if (grandchild == null) {
          continue;
        }
        updateSingleTableHeader(grandchild, collectionInfo, rowHeaders, columnHeaders);
        grandchild.recycle();
      }
    }

    child.recycle();
  }
}
 
Example 18
Source File: DirectionalTraversalStrategy.java    From talkback with Apache License 2.0 4 votes vote down vote up
/**
 * 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 19
Source File: FeedbackUtils.java    From talkback with Apache License 2.0 4 votes vote down vote up
/**
 * Gets the developer-provided text inside a node that will be used to generate spoken feedback.
 * If the node does not have any text, we attempt to get node text of any non-focusable children.
 * If there are no focusable children with text, an empty string will be returned.
 *
 * <p>Note: This method should never be called with nodes returned from
 * AccessibilityNodeInfo#obtain. These nodes do not retain children information, so this method
 * may return the incorrect text. Instead, use SwitchAccessNodeCompat#getNodeText.
 *
 * @param nodeCompat the {@link AccessibilityNodeInfoCompat} of the node from which the
 *     developer-provided text should be retrieved
 * @return the developer-provided text (content description or text) of the given node. If there
 *     is neither content description nor text inside the node or its children, then return an
 *     empty string
 */
public static String getNodeText(AccessibilityNodeInfoCompat nodeCompat) {
  CharSequence speakableText = nodeCompat.getContentDescription();

  if (StringUtils.isEmpty(speakableText)) {
    speakableText = nodeCompat.getText();
  }

  // If speakable text is empty, see if there are any non-focusable children nodes. If so, use
  // their text for the speakable text of this node. We filter out any focusable children nodes
  // to prevent duplicated speakable text from both a parent and child node.
  if (StringUtils.isEmpty(speakableText)) {
    StringBuilder builder = new StringBuilder();

    int numChildren = nodeCompat.getChildCount();
    for (int i = 0; i < numChildren; i++) {
      AccessibilityNodeInfoCompat child = nodeCompat.getChild(i);
      if ((child != null)
          && AccessibilityNodeInfoUtils.hasMinimumPixelsVisibleOnScreen(child)
          && !AccessibilityNodeInfoUtils.shouldFocusNode(child)) {
        CharSequence childText = getNodeText(child);

        if (!StringUtils.isEmpty(childText)) {
          if (builder.length() != 0) {
            builder.append(" ");
          }
          builder.append(childText);
        }
      }

      if (child != null) {
        child.recycle();
      }
    }

    speakableText = builder.toString();
  }
  if (StringUtils.isEmpty(speakableText)) {
    speakableText = "";
  }

  return speakableText.toString();
}
 
Example 20
Source File: AccessibilityUtil.java    From screenshot-tests-for-android with Apache License 2.0 4 votes vote down vote up
/**
 * Returns whether a given {@link View} will be focusable by Google's TalkBack screen reader.
 *
 * @param view The {@link View} to evaluate.
 * @return {@code boolean} if the view will be ignored by TalkBack.
 */
public static boolean isTalkbackFocusable(View view) {
  if (view == null) {
    return false;
  }

  final int important = ViewCompat.getImportantForAccessibility(view);
  if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO
      || important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
    return false;
  }

  // Go all the way up the tree to make sure no parent has hidden its descendants
  ViewParent parent = view.getParent();
  while (parent instanceof View) {
    if (ViewCompat.getImportantForAccessibility((View) parent)
        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
      return false;
    }
    parent = parent.getParent();
  }

  // Trying to evaluate the focusability of certain element types (mainly list views) can cause
  // problems when trying to determine the offset of the elements Rect relative to its parent in
  // ViewGroup.offsetRectBetweenParentAndChild. If this happens, simply return false, as this view
  // will not be focusable.
  AccessibilityNodeInfoCompat node;
  try {
    node = createNodeInfoFromView(view);
  } catch (IllegalArgumentException e) {
    return false;
  }

  if (node == null) {
    return false;
  }

  // Non-leaf nodes identical in size to their Window should not be focusable.
  if (areBoundsIdenticalToWindow(node, view) && node.getChildCount() > 0) {
    return false;
  }

  try {
    if (!node.isVisibleToUser()) {
      return false;
    }

    if (isAccessibilityFocusable(node, view)) {
      if (!hasVisibleChildren(view)) {
        // Leaves that are accessibility focusable are never ignored, even if they don't have a
        // speakable description
        return true;
      } else if (isSpeakingNode(node, view)) {
        // Node is focusable and has something to speak
        return true;
      }

      // Node is focusable and has nothing to speak
      return false;
    }

    // if view is not accessibility focusable, it needs to have text and no focusable ancestors.
    if (!hasText(node)) {
      return false;
    }

    if (!hasFocusableAncestor(node, view)) {
      return true;
    }

    return false;
  } finally {
    node.recycle();
  }
}