Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ private CaptureResult captureXml(
// Busy or animated apps can still expose a usable root; capture whatever is available.
}
}
clearAccessibilityCache(automation);

CaptureStats stats = new CaptureStats();
StringBuilder xml = new StringBuilder();
Expand All @@ -367,6 +368,38 @@ private CaptureResult captureXml(
xml.toString(), windowCount > 0, captureMode, windowCount, stats.nodeCount, stats.truncated);
}

private static void clearAccessibilityCache(UiAutomation automation) {
// Navigation 3 and any in-place content swap that keeps the same window/Activity render every
// destination inside one AndroidComposeView. A persistent helper session reuses a single
// UiAutomation connection across captures, and its per-connection accessibility node cache is
// not always invalidated by such a swap, so getWindows()/getRootInActiveWindow() can keep
// returning the previous screen. Drop the cache before each traversal so every capture re-reads
// the live tree, the way a fresh `uiautomator dump` (new connection, empty cache) does.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
try {
automation.clearCache();
return;
} catch (RuntimeException ignored) {
// Fall back to the setServiceInfo() flush below if the platform rejects the public API.
}
}
clearAccessibilityCacheViaServiceInfo(automation);
}

private static void clearAccessibilityCacheViaServiceInfo(UiAutomation automation) {
// Platforms before API 34 lack UiAutomation.clearCache(). Re-applying the current service info
// is the supported public-API way to drop the cache there: UiAutomation.setServiceInfo() calls
// AccessibilityInteractionClient.clearCache() before forwarding the config (verified in AOSP
// from API 24 through 33). Reflecting the internal clearCache() directly is not an option — it
// is a blocklisted non-SDK member, so hidden-API enforcement makes it unreachable from the
// helper process. Best-effort: if the flush fails the capture proceeds with the current tree.
try {
automation.setServiceInfo(automation.getServiceInfo());
} catch (RuntimeException ignored) {
// No reliable cache reset on this platform; leave capture behavior unchanged.
}
}

private UiAutomation getConnectedUiAutomation(long timeoutMs) throws TimeoutException {
long deadlineMs = System.currentTimeMillis() + Math.max(1, timeoutMs);
UiAutomation automation = getUiAutomation();
Expand Down
Loading