Skip to content

[Bug]: getDisplayDimension() returns null on Android < 12 due to double Matcher.find() consuming cursor state #430

Description

@julienmerconsulting

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

  1. Connect an Android < 12 device via USB with ADB enabled
  2. From any OculiX script, call ADBScreen.start(ADB_PATH)
  3. 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.

🦎

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    📋 Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions