findAndroidUiMatches function
Finds the nodes matching query, in document order, in the first tier
that has any match:
textorcontent-descequal toquery(both trimmed).- The same, ignoring case and curly quotes / no-break spaces.
resource-idequal toquery, either the fullpackage:id/nameor the barename.
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 [];
}