Summary
ADBDevice.getDisplayDimension() silently returns null on Android < 12 devices — no exception, no log, just a null bubbling upward through anything relying on ADBScreen coordinates.
Steps to reproduce
- Connect an Android < 12 device via USB with ADB enabled
- From any OculiX script, call
ADBScreen.start(ADB_PATH)
- Observe the
Dimension returned by the internal getDisplayDimension() call
Expected behavior
Returns the real device dimensions parsed from the mDefaultViewport block in adb shell dumpsys display.
Actual behavior
Returns null on every run on Android < 12 devices with a single physical display (the common case). On rare setups with a secondary display (cast / virtual output / HDMI), returns the dimensions of the wrong display.
Downstream: any script that computes coordinates relative to the reported dimension either NPEs on the null, or clicks at the wrong place silently — the failure is invisible in CI logs.
Operating system
Windows 11 (reproduce environment reported by @v-os)
Java version
25
OculiX version / artifact
Current master (commit bc102605, v4.0.0)
Where does the bug happen?
- API (Screen / Region / Pattern / Match)
- Android / ADB
Root cause
I traced the call to API/src/main/java/org/sikuli/android/ADBDevice.java:348-360. The method builds a Matcher from pattern p1, calls find() inside an if (!match.find()) guard, and then calls find() again on the same Matcher to decide whether to return dimensions.
java.util.regex.Matcher is stateful: a successful find() advances an internal cursor past the match. On Android < 12 (p1 matches), the second find() therefore searches from the position after the first hit, not from the start of the dump. In a single-display dump there is nothing more to find, so the second call returns false and the method silently falls through to return null.
On Android 12+ (p1 fails, p2 is assigned a fresh Matcher), the cursor is reset to 0, so the second find() correctly locates the first match. That code path was never broken — hence the bug remained silent until now.
Proposed direction
Store the result of the first find() in a local boolean, and reuse that value at the decision point instead of calling find() a second time. That preserves the two-pattern fallback logic (p1 then p2) without touching the Matcher state twice.
Additional context
Discovered and reported by @v-os in PR #428 (closed in favour of this issue for consolidation). Vitalii's proposed one-liner is the shape I would land — I'm reopening the surface here to attach regression coverage before merge.
@v-os — happy to hand you the follow-up PR if you'd like to see it through end-to-end, otherwise I'll land it under your Reported-by trailer for the 4.0.1 milestone.
🦎
Summary
ADBDevice.getDisplayDimension()silently returnsnullon Android < 12 devices — no exception, no log, just a null bubbling upward through anything relying onADBScreencoordinates.Steps to reproduce
ADBScreen.start(ADB_PATH)Dimensionreturned by the internalgetDisplayDimension()callExpected behavior
Returns the real device dimensions parsed from the
mDefaultViewportblock inadb shell dumpsys display.Actual behavior
Returns
nullon every run on Android < 12 devices with a single physical display (the common case). On rare setups with a secondary display (cast / virtual output / HDMI), returns the dimensions of the wrong display.Downstream: any script that computes coordinates relative to the reported dimension either NPEs on the null, or clicks at the wrong place silently — the failure is invisible in CI logs.
Operating system
Windows 11 (reproduce environment reported by @v-os)
Java version
25
OculiX version / artifact
Current
master(commitbc102605, v4.0.0)Where does the bug happen?
Root cause
I traced the call to
API/src/main/java/org/sikuli/android/ADBDevice.java:348-360. The method builds aMatcherfrom pattern p1, callsfind()inside anif (!match.find())guard, and then callsfind()again on the same Matcher to decide whether to return dimensions.java.util.regex.Matcheris stateful: a successfulfind()advances an internal cursor past the match. On Android < 12 (p1 matches), the secondfind()therefore searches from the position after the first hit, not from the start of the dump. In a single-display dump there is nothing more to find, so the second call returnsfalseand the method silently falls through toreturn null.On Android 12+ (p1 fails, p2 is assigned a fresh Matcher), the cursor is reset to 0, so the second
find()correctly locates the first match. That code path was never broken — hence the bug remained silent until now.Proposed direction
Store the result of the first
find()in a local boolean, and reuse that value at the decision point instead of callingfind()a second time. That preserves the two-pattern fallback logic (p1 then p2) without touching the Matcher state twice.Additional context
Discovered and reported by @v-os in PR #428 (closed in favour of this issue for consolidation). Vitalii's proposed one-liner is the shape I would land — I'm reopening the surface here to attach regression coverage before merge.
@v-os — happy to hand you the follow-up PR if you'd like to see it through end-to-end, otherwise I'll land it under your
Reported-bytrailer for the 4.0.1 milestone.🦎