parseAndroidUiDump function

AndroidUiDumpParse parseAndroidUiDump(
  1. String raw
)

Parses raw uiautomator dump output.

Tolerates text around the XML (the trailing UI hierchary dumped to: ... line, warnings before it). Requires a <hierarchy root and its closing tag, so an error line or a truncated dump is never mistaken for an empty screen.

Implementation

AndroidUiDumpParse parseAndroidUiDump(String raw) {
  final start = raw.indexOf('<hierarchy');
  final end = raw.lastIndexOf('</hierarchy>');
  if (start < 0 || end < start) {
    // A self-closing root means an empty window; still a valid dump.
    final selfClosing = RegExp(r'<hierarchy\b[^>]*/>').firstMatch(raw);
    if (selfClosing != null) return const AndroidUiDumpParsed([]);
    return AndroidUiDumpInvalid(_invalidReason(raw, start: start));
  }

  final xml = raw.substring(start, end);
  final nodes = <AndroidUiNode>[];
  final stack = <AndroidUiNode?>[];
  var i = 0;
  while (i < xml.length) {
    final lt = xml.indexOf('<', i);
    if (lt < 0) break;
    if (xml.startsWith('</node', lt)) {
      if (stack.isNotEmpty) stack.removeLast();
      final gt = xml.indexOf('>', lt);
      i = gt < 0 ? xml.length : gt + 1;
      continue;
    }
    if (!xml.startsWith('<node', lt) || !_isNameEnd(xml, lt + 5)) {
      i = lt + 1;
      continue;
    }
    final tag = _readTag(xml, lt + 5);
    final attrs = tag.attributes;
    final node = AndroidUiNode(
      text: (attrs['text'] ?? '').trim(),
      contentDesc: (attrs['content-desc'] ?? '').trim(),
      resourceId: (attrs['resource-id'] ?? '').trim(),
      className: attrs['class'] ?? '',
      clickable: attrs['clickable'] == 'true',
      enabled: attrs['enabled'] != 'false',
      bounds: parseAndroidBounds(attrs['bounds']),
      parent: stack.isEmpty ? null : stack.last,
    );
    nodes.add(node);
    if (!tag.selfClosing) stack.add(node);
    i = tag.end;
  }
  return AndroidUiDumpParsed(nodes);
}