screencap
changes the deviceTakes a screenshot, either to a file on the phone or to standard output as PNG.
- Command
- adb shell screencap
- Commands
- Display and capture
- Risk
- changes the device
Syntax
screencap [-p] [<device path>]Flags and arguments
| Token | Meaning |
|---|---|
| -p | Encode as PNG. Without it and without a path ending in .png the output is a raw framebuffer dump. |
| <path> | Write to a file on the device instead of to standard output. |
Examples
screencap -p /sdcard/shot.pngWrite to the phone, then pull the file. Always safe.
screencap -pPNG bytes on standard output. From a desktop, run it through `adb exec-out`, never `adb shell`.
Reading the output
Binary PNG data, or nothing at all when you gave it a path. There is no success message: the evidence is the file, or the bytes.
Output fields
| Field | Meaning |
|---|---|
| (PNG bytes) | The image, on standard output, when no path was given. |
| (no output) | The image went to the file you named. |
What access it needs
No root and no `adb root`. `screencap` is a native binary rather than a `cmd` wrapper, and the permission decision happens inside SurfaceFlinger: `validateScreenshotPermissions` lets a caller through if it is `AID_GRAPHICS`, `AID_SYSTEM`, or holds `android.permission.READ_FRAME_BUFFER` — which `com.android.shell`, the package the `shell` user runs as, is granted in its own manifest. The capture-by-display-id path is blunter still and admits `AID_ROOT`, `AID_GRAPHICS`, `AID_SYSTEM` and `AID_SHELL` directly. So an authorised cable is enough, and nothing in developer options gates it beyond USB debugging itself. The permission you conspicuously do not hold is `android.permission.CAPTURE_BLACKOUT_CONTENT`, the one that would allow secure layers into a capture, and `screencap` never asks for it — that is the mechanism behind protected video arriving as a black rectangle, not a fault in the encoder. Then there is the half of the access question that is not Android at all: writing to a path is an ordinary `open` with `O_WRONLY | O_CREAT | O_TRUNC` and mode 0664 as the `shell` user, so shared storage works, an app's private directory does not, and the refusal you get back is a plain C `errno` string rather than anything the platform dressed up.
Failure messages, decoded
| Message | Meaning |
|---|---|
| Error opening file: /data/data/com.example/shot.png (Permission denied) | The `open` failed and the text in brackets is the C library's `strerror`. The `shell` user cannot write inside another app's data directory; write to shared storage and pull the file from there. `Read-only file system` in the same position means the path is on a mounted-read-only partition, not that the screenshot failed. |
| Ambiguous file type | The format was stated twice and the two statements disagree — `-p` says PNG while a `.jpg` or `.jpeg` filename says JPEG. `screencap` refuses rather than picking one, exits 1, and writes nothing. Drop `-p` when the extension already carries the format. |
| Failed to take screenshot. Status: -1 | The capture request came back with an error status, so nothing was encoded. This is where a refusal from SurfaceFlinger surfaces on the client side — the reason itself is logged there, not printed here, so `logcat` around the same moment is the place to look. |
| Failed to get ID for any displays. | SurfaceFlinger reported no physical display to capture. Expected before boot has completed, and on headless builds that genuinely have none. It is not a permission problem, so re-running with more privilege changes nothing. |
| Error writing to stdout. (Bad file descriptor) | With no filename `screencap` duplicates its own standard output to write into, and that duplication failed. In practice it means the stream was closed under it — a pipeline whose reader exited early is the everyday cause. Nothing usable was produced; whatever the reader already had is a truncated image. |
| [Warning] Multiple displays were found, but no display id was specified! | Not a failure, and this is why it is worth listing: the capture succeeds, but on a foldable or a phone with something plugged into its video output the display it picked is not guaranteed to be the same one next time. The two lines after it tell you to pass `-d` and where to find valid ids. Treat an unattended capture without `-d` on such a device as unreliable rather than wrong. |
What it changes, and the way back
With no path it writes PNG bytes to standard output and changes nothing on the device. With a device path it creates that file, or overwrites and truncates it if it already exists, so a wrong path can replace a real file and the old contents are gone — Android has no trash can. The file also occupies storage until you remove it: `rm <path>` is the way back, and pulling it to the computer first is what makes a screenshot worth keeping.
Version and vendor notes
- Streaming the bytes needs `adb exec-out screencap -p`, not `adb shell`. The plain shell service gives the command a terminal, and a terminal turns every 0x0A byte into 0x0D 0x0A — which corrupts a PNG in a way that is invisible until something tries to decode it.
- Content protected by DRM comes out black. The compositor refuses to hand those buffers over, so this is a property of the platform rather than a bug in the capture.
- For a screenshot without your own notification icons in it, put SystemUI into demo mode first with `am broadcast`.
We have a UI for this
Android ADB Toolbox runs this command over USB and parses what comes back — a Chromium browser, no SDK, nothing uploaded.
Related commands
Commands that turn up in the same session as screencap.
| Command | What it does |
|---|---|
| screenrecord | Records the screen to an MP4 file on the device. |
| am broadcast | Sends a broadcast intent, which is how SystemUI demo mode and other platform receivers are driven from a shell. |
| wm size | Reports the display's resolution, or overrides it so the phone pretends to be a different screen. |
More Display and capture commands
| Command | What it does |
|---|---|
| wm density | Reports the display density, or overrides it — which is what actually moves a layout breakpoint. |
Checked against AOSP, toybox and kernel sources while writing the parsers behind our Web ADB tools. Where a behaviour genuinely varies by Android version or manufacturer, this page says so instead of giving a number that would be wrong on half the phones out there.