parseAndroidUiDump function
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);
}