findAndroidUiMatches function

List<AndroidUiMatch> findAndroidUiMatches(
  1. List<AndroidUiNode> nodes,
  2. String query
)

Finds the nodes matching query, in document order, in the first tier that has any match:

  1. text or content-desc equal to query (both trimmed).
  2. The same, ignoring case and curly quotes / no-break spaces.
  3. resource-id equal to query, either the full package:id/name or the bare name.

Skipped: enabled="false" nodes, and nodes whose tap point is off screen (no bounds, or a center outside the bounds of the top-level windows). A match nested in an earlier match that belongs to the same clickable element (a button's content-desc and its inner text) counts once; two separate labels inside one container are two matches.

Implementation

List<AndroidUiMatch> findAndroidUiMatches(List<AndroidUiNode> nodes, String query) {
  final q = query.trim();
  if (q.isEmpty) return const [];
  final folded = foldNativeLabel(q);
  final screen = _screenBounds(nodes);

  final tiers = <bool Function(AndroidUiNode)>[
    (n) => n.text == q || n.contentDesc == q,
    (n) => foldNativeLabel(n.text) == folded || foldNativeLabel(n.contentDesc) == folded,
    (n) => n.resourceId.isNotEmpty && (n.resourceId == q || n.resourceId.split(':id/').last == q),
  ];

  for (final matches in tiers) {
    final result = <AndroidUiMatch>[];
    for (final node in nodes) {
      if (!node.enabled || !matches(node)) continue;
      final target = _tapTarget(node);
      if (target == null) continue;
      final bounds = target.bounds!;
      if (screen != null && !_contains(screen, bounds.centerX, bounds.centerY)) continue;
      if (result.any((m) => _sameElement(m.node, node))) continue;
      result.add(AndroidUiMatch(node: node, target: target, bounds: bounds));
    }
    if (result.isNotEmpty) return result;
  }
  return const [];
}