Skip to content

Conversation

@gmegidish
Copy link
Member

@gmegidish gmegidish commented Nov 26, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced screenshot capture with multi-display support, enabling automatic detection and capture from the first active display on devices with multiple screens.
    • Improved robustness across Android versions with multiple fallback mechanisms for display identification and error handling.
  • Tests

    • Added unit tests for display ID parsing from various Android system outputs.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

Enhanced screenshot capture with multi-display support. Added display detection utilities to query SurfaceFlinger for display count and identify the first active display. Modified TakeScreenshot to handle both single and multi-display Android devices with multiple fallback parsing strategies for display ID extraction.

Changes

Cohort / File(s) Summary
Multi-display screenshot support
devices/android.go
Added getDisplayCount() to query display count; getFirstDisplayId() to locate active display; captureScreenshot() for display-specific captures; three parser functions (parseDisplayIdFromCmdDisplay, parseDisplayIdFromDumpsysViewport, parseDisplayIdFromDumpsysState) to extract display IDs from various Android command outputs; refactored TakeScreenshot() to use multi-display logic with fallbacks.
Display parsing unit tests
devices/android_display_test.go
Added comprehensive test coverage for display ID extraction with three test groups: Test_parseDisplayIdFromCmdDisplay, Test_parseDisplayIdFromDumpsysViewport, Test_parseDisplayIdFromDumpsysState; each validates parsing against valid and invalid inputs.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant TakeScreenshot
    participant getDisplayCount
    participant SurfaceFlinger as SurfaceFlinger (adb)
    participant getFirstDisplayId
    participant Parser
    participant captureScreenshot
    
    Caller->>TakeScreenshot: Take screenshot
    TakeScreenshot->>getDisplayCount: Query display count
    getDisplayCount->>SurfaceFlinger: adb shell getprop | grep display
    SurfaceFlinger-->>getDisplayCount: Display info
    getDisplayCount-->>TakeScreenshot: Count (1 or more)
    
    alt Single Display
        TakeScreenshot->>captureScreenshot: capture(empty)
        captureScreenshot->>SurfaceFlinger: adb shell screencap
        SurfaceFlinger-->>captureScreenshot: Screenshot data
    else Multiple Displays
        TakeScreenshot->>getFirstDisplayId: Get first active display
        getFirstDisplayId->>SurfaceFlinger: cmd display get-displays / dumpsys display
        SurfaceFlinger-->>getFirstDisplayId: Display output
        getFirstDisplayId->>Parser: Parse display ID
        Parser-->>getFirstDisplayId: Display ID
        getFirstDisplayId-->>TakeScreenshot: Display ID (or empty)
        TakeScreenshot->>captureScreenshot: capture(displayID)
        captureScreenshot->>SurfaceFlinger: adb shell screencap -d displayID
        SurfaceFlinger-->>captureScreenshot: Screenshot data
    end
    
    captureScreenshot-->>TakeScreenshot: []byte or error
    TakeScreenshot-->>Caller: Screenshot result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Multiple parsing functions: Each parser (parseDisplayIdFromCmdDisplay, parseDisplayIdFromDumpsysViewport, parseDisplayIdFromDumpsysState) uses different regex or string-matching logic—requires individual verification of parsing correctness
  • Error handling paths: Several fallback chains in TakeScreenshot and getFirstDisplayId need careful review to ensure appropriate behavior across single/multi-display scenarios
  • ADB command invocations: Verify correctness of display-specific screenshot capture syntax and SurfaceFlinger property queries across Android versions
  • Test coverage validation: Confirm test cases adequately cover edge cases, malformed inputs, and Android version variations

Poem

🐰 Hops through screens with gleeful cheer,
Multi-display magic's here!
Parsing IDs from depths so deep,
Now all your rabbits' screenshots keep! ✨📱

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding multi-display support to Android screenshot functionality, with a concrete example (Pro Fold 9) making it clear and specific.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-screenshot-from-multi-display-android

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
devices/android.go (1)

208-216: Viewport parser is precise and appropriately scoped

The DisplayViewport{type=INTERNAL ... isActive=true ... uniqueId='...'} regex is nicely constrained to the internal active viewport, and trimming the local: prefix matches how IDs are used later. If this becomes hot, consider hoisting the compiled regexp to a package‑level var, but that’s not required now.

devices/android_display_test.go (2)

5-38: Align cmd-display test fixtures with actual adb output

The tests cover single, multi, and invalid cases nicely, but the output strings start Display id 0: ... at column 0, whereas real adb shell cmd display get-displays output is typically indented (" Display id 0: ...") before the “Display id” token.

Once parseDisplayIdFromCmdDisplay is updated to TrimSpace each line, consider also updating at least one test case to include the leading spaces so the tests reflect real-world output and guard against regressions.


40-72: Viewport parser tests give good coverage of expected scenarios

These cases (single active, two viewports with first active, invalid input) match the regex’s intent and should catch most regressions in parseDisplayIdFromDumpsysViewport. If you later need to support external displays, you could add a case with type=EXTERNAL as a negative test to ensure INTERNAL-only behavior remains intentional, but that’s optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12528fd and 97e35c4.

📒 Files selected for processing (2)
  • devices/android.go (1 hunks)
  • devices/android_display_test.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint
🔇 Additional comments (6)
devices/android.go (5)

172-188: Display count heuristic is reasonable and fails safe

Using dumpsys SurfaceFlinger --display-id and falling back to 1 on error is a pragmatic choice; worst case you behave like a single‑display device and rely on screencap’s default. This looks good as-is.


218-226: State parser is a reasonable heuristic for legacy dumpsys formats

Using Display Id=(\d+)[\s\S]*?Display State=ON to derive the first ON display ID is a pragmatic last‑resort fallback when uniqueId isn’t present. It won’t be perfect if dumpsys formats change, but given it’s only used when higher‑fidelity parsers fail, this trade‑off seems acceptable.


228-253: getFirstDisplayId fallback chain is solid, but relies on screencap’s -d semantics

The order (cmd display → dumpsys viewport → dumpsys state) is sensible and gives you multiple chances to find an active internal display before returning "".

One thing to double‑check on your target devices/Android versions is that the IDs you extract here (trimmed uniqueId values or numeric “Display Id=…”) are always valid arguments to screencap -d, as some docs/examples derive IDs directly from dumpsys SurfaceFlinger --display-id / --displays. Given the diversity of multi‑display setups (physical vs virtual), it’s worth validating this empirically on the devices you care about.


255-265: captureScreenshot composition and error handling look correct

Building the adb args via exec-out screencap -p and conditionally appending -d <displayID> is correct, and wrapping failures as fmt.Errorf("failed to take screenshot: %w", err) gives callers useful context. The use of runAdbCommand keeps the -s <device> handling consistent with the rest of the code.


268-285: Multi-display screenshot behavior is well-fallbacked

TakeScreenshot now:

  • Treats displayCount <= 1 as legacy/single‑display and uses the default screencap.
  • For multi‑display, tries to locate the first active display via getFirstDisplayId, falling back to default screencap if parsing fails.

This gives foldables and multi‑display emulators better behavior without regressing older devices, and the “hope for the best” default path is a sensible last resort.

devices/android_display_test.go (1)

74-110: State parser tests are concise and sufficient

The tests exercise the core contract of parseDisplayIdFromDumpsysState: pick the first ON display ID and return "" for garbage input. Given this function is already a fallback, this level of coverage is appropriate.

Comment on lines +190 to +206
// look for lines like "Display id X, ... state ON, ... uniqueId "..."
if strings.HasPrefix(line, "Display id ") &&
strings.Contains(line, ", state ON,") &&
strings.Contains(line, ", uniqueId ") {
re := regexp.MustCompile(`uniqueId "([^"]+)"`)
matches := re.FindStringSubmatch(line)
if len(matches) == 2 {
return strings.TrimPrefix(matches[1], "local:")
}
}
}
return ""
}

// parseDisplayIdFromDumpsysViewport extracts display ID from dumpsys DisplayViewport entries
func parseDisplayIdFromDumpsysViewport(dumpsys string) string {
re := regexp.MustCompile(`DisplayViewport\{type=INTERNAL[^}]*isActive=true[^}]*uniqueId='([^']+)'`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

parseDisplayIdFromCmdDisplay likely misses real output due to indentation

cmd display get-displays output is typically indented (" Display id 0: ...") rather than starting at column 0, so strings.HasPrefix(line, "Display id ") will not match and this parser will always return "", forcing the dumpsys fallback.

Suggest trimming leading/trailing whitespace before the prefix check, and updating tests to reflect the actual output shape:

 func parseDisplayIdFromCmdDisplay(output string) string {
 	lines := strings.Split(output, "\n")
 	for _, line := range lines {
+		line = strings.TrimSpace(line)
 		// look for lines like "Display id X, ... state ON, ... uniqueId "..."
 		if strings.HasPrefix(line, "Display id ") &&
 			strings.Contains(line, ", state ON,") &&
 			strings.Contains(line, ", uniqueId ") {
 			re := regexp.MustCompile(`uniqueId "([^"]+)"`)

You may also want a test case whose output has indented Display id lines so this behavior is locked in.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// parseDisplayIdFromCmdDisplay extracts display ID from "cmd display get-displays" output (Android 11+)
func parseDisplayIdFromCmdDisplay(output string) string {
lines := strings.Split(output, "\n")
for _, line := range lines {
// look for lines like "Display id X, ... state ON, ... uniqueId "..."
if strings.HasPrefix(line, "Display id ") &&
strings.Contains(line, ", state ON,") &&
strings.Contains(line, ", uniqueId ") {
re := regexp.MustCompile(`uniqueId "([^"]+)"`)
matches := re.FindStringSubmatch(line)
if len(matches) == 2 {
return strings.TrimPrefix(matches[1], "local:")
}
}
}
return ""
}
// parseDisplayIdFromCmdDisplay extracts display ID from "cmd display get-displays" output (Android 11+)
func parseDisplayIdFromCmdDisplay(output string) string {
lines := strings.Split(output, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
// look for lines like "Display id X, ... state ON, ... uniqueId "..."
if strings.HasPrefix(line, "Display id ") &&
strings.Contains(line, ", state ON,") &&
strings.Contains(line, ", uniqueId ") {
re := regexp.MustCompile(`uniqueId "([^"]+)"`)
matches := re.FindStringSubmatch(line)
if len(matches) == 2 {
return strings.TrimPrefix(matches[1], "local:")
}
}
}
return ""
}
🤖 Prompt for AI Agents
In devices/android.go around lines 190 to 206, the parser ignores indented lines
because it uses strings.HasPrefix(line, "Display id ") on raw lines; trim
leading/trailing whitespace on each line (e.g., call strings.TrimSpace or
TrimLeft) before doing the HasPrefix and subsequent Contains checks so indented
"  Display id ..." matches, keep the regex logic the same, and add/update unit
tests to include an indented Display id example to ensure the behavior is locked
in.

@gmegidish gmegidish merged commit 63e3ee9 into main Nov 26, 2025
15 checks passed
@gmegidish gmegidish deleted the fix-screenshot-from-multi-display-android branch November 26, 2025 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants