11package com .callstack .agentdevice .snapshothelper ;
22
3+ import android .accessibilityservice .AccessibilityServiceInfo ;
34import android .app .Instrumentation ;
45import android .app .UiAutomation ;
56import android .graphics .Rect ;
67import android .os .Bundle ;
78import android .util .Base64 ;
89import android .view .accessibility .AccessibilityNodeInfo ;
10+ import android .view .accessibility .AccessibilityWindowInfo ;
911import java .nio .charset .StandardCharsets ;
12+ import java .util .List ;
1013import java .util .Locale ;
1114import java .util .concurrent .TimeoutException ;
1215
@@ -52,6 +55,8 @@ public void onStart() {
5255 emitChunks (capture .xml );
5356 result .putString ("ok" , "true" );
5457 result .putString ("rootPresent" , Boolean .toString (capture .rootPresent ));
58+ result .putString ("captureMode" , capture .captureMode );
59+ result .putString ("windowCount" , Integer .toString (capture .windowCount ));
5560 result .putString ("nodeCount" , Integer .toString (capture .nodeCount ));
5661 result .putString ("truncated" , Boolean .toString (capture .truncated ));
5762 result .putString ("elapsedMs" , Long .toString (System .currentTimeMillis () - startedAtMs ));
@@ -70,6 +75,7 @@ private CaptureResult captureXml(
7075 long waitForIdleTimeoutMs , long timeoutMs , int maxDepth , int maxNodes )
7176 throws TimeoutException {
7277 UiAutomation automation = getUiAutomation ();
78+ enableInteractiveWindowRetrieval (automation );
7379 if (waitForIdleTimeoutMs > 0 ) {
7480 try {
7581 // Best-effort settle: avoids empty roots without inheriting UIAutomator's long idle wait.
@@ -79,19 +85,87 @@ private CaptureResult captureXml(
7985 }
8086 }
8187
82- AccessibilityNodeInfo root = automation .getRootInActiveWindow ();
8388 CaptureStats stats = new CaptureStats ();
8489 StringBuilder xml = new StringBuilder ();
8590 xml .append ("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" );
8691 xml .append ("<hierarchy rotation=\" 0\" >" );
87- if (root != null ) {
88- appendNode (xml , root , 0 , 0 , maxDepth , maxNodes , stats );
92+ int windowCount = appendInteractiveWindowRoots (xml , automation , maxDepth , maxNodes , stats );
93+ String captureMode = "interactive-windows" ;
94+ if (windowCount == 0 ) {
95+ AccessibilityNodeInfo root = automation .getRootInActiveWindow ();
96+ try {
97+ if (root != null ) {
98+ appendNode (xml , root , 0 , 0 , maxDepth , maxNodes , stats );
99+ windowCount = 1 ;
100+ }
101+ captureMode = "active-window" ;
102+ } finally {
103+ if (root != null ) {
104+ root .recycle ();
105+ }
106+ }
89107 }
90108 xml .append ("</hierarchy>" );
91- if (root != null ) {
92- root .recycle ();
109+ return new CaptureResult (
110+ xml .toString (), windowCount > 0 , captureMode , windowCount , stats .nodeCount , stats .truncated );
111+ }
112+
113+ private static void enableInteractiveWindowRetrieval (UiAutomation automation ) {
114+ AccessibilityServiceInfo serviceInfo ;
115+ try {
116+ serviceInfo = automation .getServiceInfo ();
117+ } catch (RuntimeException error ) {
118+ return ;
119+ }
120+ if (serviceInfo == null ) {
121+ return ;
122+ }
123+ if ((serviceInfo .flags & AccessibilityServiceInfo .FLAG_RETRIEVE_INTERACTIVE_WINDOWS ) != 0 ) {
124+ return ;
125+ }
126+ serviceInfo .flags |= AccessibilityServiceInfo .FLAG_RETRIEVE_INTERACTIVE_WINDOWS ;
127+ try {
128+ automation .setServiceInfo (serviceInfo );
129+ } catch (RuntimeException ignored ) {
130+ // Fall back to active-window capture if the platform rejects dynamic service flags.
131+ }
132+ }
133+
134+ private static int appendInteractiveWindowRoots (
135+ StringBuilder xml ,
136+ UiAutomation automation ,
137+ int maxDepth ,
138+ int maxNodes ,
139+ CaptureStats stats ) {
140+ List <AccessibilityWindowInfo > windows ;
141+ try {
142+ windows = automation .getWindows ();
143+ } catch (RuntimeException error ) {
144+ return 0 ;
145+ }
146+ int windowCount = 0 ;
147+ for (int index = 0 ; index < windows .size (); index += 1 ) {
148+ if (stats .nodeCount >= maxNodes ) {
149+ stats .truncated = true ;
150+ break ;
151+ }
152+ AccessibilityWindowInfo window = windows .get (index );
153+ AccessibilityNodeInfo root = null ;
154+ try {
155+ root = window .getRoot ();
156+ if (root == null ) {
157+ continue ;
158+ }
159+ appendNode (xml , root , windowCount , 0 , maxDepth , maxNodes , stats );
160+ windowCount += 1 ;
161+ } finally {
162+ if (root != null ) {
163+ root .recycle ();
164+ }
165+ window .recycle ();
166+ }
93167 }
94- return new CaptureResult ( xml . toString (), root != null , stats . nodeCount , stats . truncated ) ;
168+ return windowCount ;
95169 }
96170
97171 private void emitChunks (String payload ) {
@@ -262,12 +336,22 @@ private static final class CaptureStats {
262336 private static final class CaptureResult {
263337 final String xml ;
264338 final boolean rootPresent ;
339+ final String captureMode ;
340+ final int windowCount ;
265341 final int nodeCount ;
266342 final boolean truncated ;
267343
268- CaptureResult (String xml , boolean rootPresent , int nodeCount , boolean truncated ) {
344+ CaptureResult (
345+ String xml ,
346+ boolean rootPresent ,
347+ String captureMode ,
348+ int windowCount ,
349+ int nodeCount ,
350+ boolean truncated ) {
269351 this .xml = xml ;
270352 this .rootPresent = rootPresent ;
353+ this .captureMode = captureMode ;
354+ this .windowCount = windowCount ;
271355 this .nodeCount = nodeCount ;
272356 this .truncated = truncated ;
273357 }
0 commit comments