Skip to content

Add AdbRunner screencap and screenrecord APIs #349

@rmarinho

Description

@rmarinho

Summary

Add screen capture APIs to Xamarin.Android.Tools.AdbRunner so the MAUI DevTools CLI can capture screenshots and screen recordings from Android devices/emulators without consumers shelling out to adb shell screencap / screenrecord.

Context

Once the DevFlow agent is up, maui devflow ui screenshot captures the running MAUI app's view. But the agent is unavailable when (a) the app hasn't launched yet, (b) the app crashed at startup, (c) you want to capture the full device screen including system UI, or (d) you're driving a non-MAUI app from the same harness. For those, we need a typed adb-level wrapper. See maui-labs#197 audit.

Proposed API

namespace Xamarin.Android.Tools;

public partial class AdbRunner
{
    /// adb -s <serial> shell screencap -p /sdcard/<temp>.png && adb pull && adb shell rm
    public virtual Task<AdbScreenshotResult> ScreenshotAsync(
        string serial,
        string outputPath,
        ScreenshotOptions? options = null,
        CancellationToken cancellationToken = default);

    /// adb -s <serial> shell screenrecord [--time-limit N] [--bit-rate B] [--size WxH] /sdcard/<temp>.mp4
    /// Returns a handle that the consumer disposes to stop the recording (sends SIGINT to screenrecord, then pulls).
    public virtual IAsyncDisposable StartScreenRecordAsync(
        string serial,
        string outputPath,
        ScreenRecordOptions? options = null,
        CancellationToken cancellationToken = default);
}

public record ScreenshotOptions(
    ScreenshotFormat Format = ScreenshotFormat.Png,
    /// If true, capture the display via `screencap -d <displayId>`. Useful for foldables / external displays.
    int? DisplayId = null);

public enum ScreenshotFormat { Png /* default */, Jpeg /* via -j */ }

public record AdbScreenshotResult(string OutputPath, int Width, int Height, long FileSizeBytes);

public record ScreenRecordOptions(
    TimeSpan? TimeLimit = null,           // default 180s, max 180s per screenrecord
    int? BitRateBitsPerSecond = null,     // default 4000000
    (int Width, int Height)? Size = null, // default native
    bool VerboseStderr = false);

Implementation notes:

  • Both methods write to a temp path under /sdcard/ (or getprop EXTERNAL_STORAGE), pull, and delete. Use a UUID in the filename to avoid clashes between concurrent calls.
  • StartScreenRecordAsync returns IAsyncDisposable.DisposeAsync() which: kills the device-side screenrecord process (find via pidof screenrecord then kill -2), waits a moment for the file to flush, pulls it, deletes the device copy. If the kill fails or the file is empty, DisposeAsync should throw so consumers know the recording was lost.
  • ScreenshotResult.Width/Height populated by reading the PNG header after pull (use existing image-header parsing if available; otherwise just leave 0/0 and let consumers re-read the file).

Consumer

  • MAUI DevTools CLI (dotnet/maui-labs) — maui android device screenshot [--output …] [--json] and maui android device record-video [--output …] [--time-limit …] [--json]. See maui-labs#197 audit.
  • DevFlow integration tests for full-screen captures during emulator-pre-launch and crash-on-startup repros.
  • Bug report tooling — a scripted "capture device state" command that bundles a screenshot, dumpsys, and getprop output.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions