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
Summary
Add screen capture APIs to
Xamarin.Android.Tools.AdbRunnerso the MAUI DevTools CLI can capture screenshots and screen recordings from Android devices/emulators without consumers shelling out toadb shell screencap/screenrecord.Context
Once the DevFlow agent is up,
maui devflow ui screenshotcaptures 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 typedadb-level wrapper. See maui-labs#197 audit.Proposed API
Implementation notes:
/sdcard/(orgetprop EXTERNAL_STORAGE), pull, and delete. Use a UUID in the filename to avoid clashes between concurrent calls.StartScreenRecordAsyncreturnsIAsyncDisposable.DisposeAsync()which: kills the device-sidescreenrecordprocess (find viapidof screenrecordthenkill -2), waits a moment for the file to flush, pulls it, deletes the device copy. If the kill fails or the file is empty,DisposeAsyncshould throw so consumers know the recording was lost.ScreenshotResult.Width/Heightpopulated 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 android device screenshot [--output …] [--json]andmaui android device record-video [--output …] [--time-limit …] [--json]. See maui-labs#197 audit.dumpsys, andgetpropoutput.Related
references/android.md"Raw fallbacks not yet inmauiCLI" section