Refresh AccessibilityNodeInfo and wait for the nodes become stable.
Root cause:
The AccessibilityOverlayTest was flaky because:
- We didn’t refresh the accessibility node to be used as the baseline
- The node infos are not stable yet when we compare the expected and the actual
Bug: 296132856
Test: atest CtsAccessibilityServiceTestCases:android.accessibilityservice.cts.AccessibilityOverlayTest#testA11yServiceShowsWindowEmbeddedOverlayWithoutCallback_shouldAppearAndDisappear --iteration 100
Test: atest AccessibilityOverlayTest
Change-Id: I14c0dbf20b974b59035beb2b9f4a2d812ccb8d25
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
index 92482d9b..1c84ac2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -75,6 +75,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.Predicate;
@@ -220,6 +221,7 @@
private void doOverlayWindowTest(Executor executor, ResultCapturingCallback callback)
throws Exception {
// Show an activity on screen.
+ final StringBuilder timeoutExceptionRecords = new StringBuilder();
final Activity activity =
launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
sInstrumentation,
@@ -237,6 +239,7 @@
final SurfaceControl sc = viewHost.getSurfacePackage().getSurfaceControl();
final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setVisibility(sc, true).apply();
+ transaction.close();
// Create an accessibility overlay hosting a FrameLayout with the same size
// as the activity's root node bounds.
@@ -282,38 +285,54 @@
mService.attachAccessibilityOverlayToWindow(
activityRootNode.getWindowId(), sc, executor, callback),
(event) -> {
+ // Wait until the overlay window is added
final AccessibilityWindowInfo overlayWindow =
ActivityLaunchUtils.findWindowByTitle(sUiAutomation, overlayTitle);
- if (overlayWindow == null) {
+ if (overlayWindow == null || overlayWindow.getType()
+ != AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
return false;
}
- if (overlayWindow.getType()
- == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
- final AccessibilityNodeInfo overlayButtonNode =
- overlayWindow
- .getRoot()
- .findAccessibilityNodeInfosByText(buttonText)
- .get(0);
- final Rect expected = new Rect();
- final Rect actual = new Rect();
+ // Refresh the activity's button node to ensure the AccessibilityNodeInfo
+ // is the latest
+ activityNodeToDrawOver.refresh();
- // The overlay button should have the same window-space and screen-space
- // bounds as the view in the activity, as configured above.
- activityNodeToDrawOver.getBoundsInWindow(expected);
- overlayButtonNode.getBoundsInWindow(actual);
- assertThat(actual.isEmpty()).isFalse();
- assertThat(actual).isEqualTo(expected);
- activityNodeToDrawOver.getBoundsInScreen(expected);
- overlayButtonNode.getBoundsInScreen(actual);
- assertThat(actual.isEmpty()).isFalse();
- assertThat(actual).isEqualTo(expected);
- return true;
- }
- return false;
+ // Wait until overlay is drawn on correct location
+ final AccessibilityNodeInfo overlayButtonNode = overlayWindow
+ .getRoot()
+ .findAccessibilityNodeInfosByText(buttonText)
+ .get(0);
+ final Rect expectedBoundsInWindow = new Rect();
+ final Rect actualBoundsInWindow = new Rect();
+ activityNodeToDrawOver.getBoundsInWindow(expectedBoundsInWindow);
+ overlayButtonNode.getBoundsInWindow(actualBoundsInWindow);
+
+ final Rect expectedBoundsInScreen = new Rect();
+ final Rect actualBoundsInScreen = new Rect();
+ activityNodeToDrawOver.getBoundsInScreen(expectedBoundsInScreen);
+ overlayButtonNode.getBoundsInScreen(actualBoundsInScreen);
+
+ // Stores the related information including event, bounds
+ // as a timeout exception record.
+ timeoutExceptionRecords.append(String.format("""
+ { Received event: %s }
+ Expected bounds in window: %s, actual bounds in window: %s
+ Expected bounds in screen: %s, actual bounds in screen: %s
+ """,
+ event, expectedBoundsInWindow, actualBoundsInWindow,
+ expectedBoundsInScreen, actualBoundsInScreen));
+
+ // The overlay button should have the same window-space and screen-space
+ // bounds as the view in the activity, as configured above.
+ return actualBoundsInWindow.equals(expectedBoundsInWindow)
+ && actualBoundsInScreen.equals(expectedBoundsInScreen);
},
AsyncUtils.DEFAULT_TIMEOUT_MS);
+
checkTrustedOverlayExists(overlayTitle);
removeOverlayAndCheck(sc, overlayTitle);
+ } catch (TimeoutException timeout) {
+ throw new TimeoutException(timeout.getMessage() + "\n\nTimeout exception records : \n"
+ + timeoutExceptionRecords);
} finally {
if (activity != null) {
activity.finish();