Skip to content

Commit ecf2200

Browse files
committed
fix: guard android drawing order metadata
1 parent 710a4ca commit ecf2200

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

android-snapshot-helper/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ The APK emits instrumentation status records using
5151
The XML node attributes intentionally mirror fields consumed by the host parser, including
5252
`visible-to-user`, `drawing-order`, bounds, text/description/id, interaction booleans, and window
5353
metadata on window roots. `drawing-order` lets the host suppress covered same-window surfaces that
54-
the helper traversal can receive even when they are not user-reachable.
54+
the helper traversal can receive even when they are not user-reachable. The helper emits
55+
`drawing-order` on Android API 24+ and omits it on API 23, where the platform API is unavailable.
5556

5657
Each XML chunk is sent with:
5758

android-snapshot-helper/src/main/java/com/callstack/agentdevice/snapshothelper/SnapshotInstrumentation.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.app.Instrumentation;
55
import android.app.UiAutomation;
66
import android.graphics.Rect;
7+
import android.os.Build;
78
import android.os.Bundle;
89
import android.util.Base64;
910
import android.view.accessibility.AccessibilityNodeInfo;
@@ -513,7 +514,7 @@ private static void appendNode(
513514
appendNonEmptyAttribute(xml, "package", node.getPackageName());
514515
appendNonEmptyAttribute(xml, "content-desc", node.getContentDescription());
515516
appendAttribute(xml, "visible-to-user", Boolean.toString(node.isVisibleToUser()));
516-
appendAttribute(xml, "drawing-order", Integer.toString(node.getDrawingOrder()));
517+
appendDrawingOrderAttribute(xml, node);
517518
appendTrueAttribute(xml, "clickable", node.isClickable());
518519
appendAttribute(xml, "enabled", Boolean.toString(node.isEnabled()));
519520
appendTrueAttribute(xml, "focusable", node.isFocusable());
@@ -585,6 +586,12 @@ private static void appendTrueAttribute(StringBuilder xml, String name, boolean
585586
}
586587
}
587588

589+
private static void appendDrawingOrderAttribute(StringBuilder xml, AccessibilityNodeInfo node) {
590+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
591+
appendAttribute(xml, "drawing-order", Integer.toString(node.getDrawingOrder()));
592+
}
593+
}
594+
588595
private static void appendWindowMetadata(StringBuilder xml, WindowMetadata metadata) {
589596
appendAttribute(xml, "window-index", Integer.toString(metadata.index));
590597
appendAttribute(xml, "window-type", Integer.toString(metadata.type));

src/platforms/android/__tests__/index.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,52 @@ test('parseUiHierarchy keeps lower siblings when drawing-order metadata is unava
383383
);
384384
});
385385

386+
test('parseUiHierarchy keeps overlapping siblings when drawing-order ties', () => {
387+
const xml = `<hierarchy>
388+
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="0">
389+
<node class="android.view.ViewGroup" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="1">
390+
<node class="android.widget.Button" text="First tied action" bounds="[24,420][366,480]" clickable="true" enabled="true" visible-to-user="true" drawing-order="1"/>
391+
</node>
392+
<node class="android.view.ViewGroup" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="1">
393+
<node class="android.widget.Button" text="Second tied action" bounds="[0,220][280,280]" clickable="true" enabled="true" visible-to-user="true" drawing-order="1"/>
394+
</node>
395+
</node>
396+
</hierarchy>`;
397+
398+
const result = parseUiHierarchy(xml, 800, { raw: true });
399+
assert.equal(
400+
result.nodes.some((node) => node.label === 'First tied action'),
401+
true,
402+
);
403+
assert.equal(
404+
result.nodes.some((node) => node.label === 'Second tied action'),
405+
true,
406+
);
407+
});
408+
409+
test('parseUiHierarchy keeps lower siblings below the covered-area threshold', () => {
410+
const xml = `<hierarchy>
411+
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="0">
412+
<node class="android.view.ViewGroup" bounds="[0,0][390,717]" visible-to-user="true" drawing-order="2">
413+
<node class="android.widget.Button" text="Partial overlay action" bounds="[24,420][366,480]" clickable="true" enabled="true" visible-to-user="true" drawing-order="1"/>
414+
</node>
415+
<node class="android.view.ViewGroup" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="1">
416+
<node class="android.widget.Button" text="Mostly visible action" bounds="[0,760][280,820]" clickable="true" enabled="true" visible-to-user="true" drawing-order="1"/>
417+
</node>
418+
</node>
419+
</hierarchy>`;
420+
421+
const result = parseUiHierarchy(xml, 800, { raw: true });
422+
assert.equal(
423+
result.nodes.some((node) => node.label === 'Partial overlay action'),
424+
true,
425+
);
426+
assert.equal(
427+
result.nodes.some((node) => node.label === 'Mostly visible action'),
428+
true,
429+
);
430+
});
431+
386432
test('parseUiHierarchy keeps lower siblings covered only by non-agent-visible overlays', () => {
387433
const xml = `<hierarchy>
388434
<node class="android.widget.FrameLayout" bounds="[0,0][390,844]" visible-to-user="true" drawing-order="0">

0 commit comments

Comments
 (0)