Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

headroid (Python host)

Run Android apps on a hidden virtual display and drive them from Python. No runtime dependencies; needs adb on PATH and an Android 11+ phone with USB debugging on (Android 14-16 are verified).

1. Install

pip install --pre headroid

(--pre while headroid is an alpha. The wheel is also attached to each GitHub release.)

The wheel bundles the device-server jar, so there is nothing else to build. From a source checkout instead: cd device-server && ./gradlew :server:headroidJar, then pip install -e packages/headroid; the jar is found in the build folder (or set HEADROID_SERVER_JAR).

2. Check your setup

headroid devices
headroid doctor            # add --serial <serial> when more than one device is attached

doctor checks adb, the device and its state, the Android version, the server jar, and that the server can create a hidden display and take a screenshot. Each line is PASS, WARN or FAIL with a one-line fix. It exits 0 unless something FAILed.

Xiaomi / Redmi / POCO: in Developer options enable "USB debugging (Security settings)" (needed for input) and "Install via USB" (needed to install apps).

3. See an app on a hidden display

headroid launch dev.headroid.sample --screenshot shot.png

This creates a hidden display, launches the app on it, waits 3 seconds (--wait), saves the screenshot and releases the display. Nothing appears on the phone's own screen.

4. Drive it from Python

from headroid import Device

with Device.connect() as dev:                      # jar found automatically
    d = dev.create_display(720, 1600, 320)         # hidden virtual display
    d.launch("dev.headroid.sample")
    d.click(rid="nav_login")                       # waits until the node has settled, then taps it
    d.fill("demo@headroid.dev", rid="login_email")
    d.fill("headroid", rid="login_password")
    d.click(rid="login_submit")
    d.wait_for(rid="form_name")                    # raises TimeoutError naming what it waited for
    d.screenshot("form.png")

click, fill and wait_for take text=, rid= or desc= and a timeout (default 10 s). They wait until the node is visible, fully on the display and at the same position on two reads in a row, because during an activity transition nodes are reported at animated positions and an immediate tap misses. Lower-level calls (tap, swipe, key, type, tree, find) are also available.

What stays with you, and what does not

  • Taps, typing and the keyboard go to the hidden display only; your screen keeps focus.
  • launch mutes the app's sound and vibration (app-ops) and restores them when the app is stopped or the session ends, even if the server was killed (the next server start restores them). Pass mute=False to hear it. App-ops are per app: while muted, the same app is silent on your screen too.
  • Notifications still reach your notification shade by default. launch(..., mute_notifications=True) blocks them by revoking the notification permission while the session runs; the app can see that and may ask for the permission again, so it is opt-in, and it is skipped when the app is already running.
  • Toasts, dialogs and permission prompts appear on the hidden display.
  • A Wi-Fi drop does not end the session: the server keeps it for 60 s, the host reconnects (finding the phone even under a new adb serial) and resends reads; an action that was in flight raises ConnectionError saying it may or may not have run. If no host returns within 60 s, the server tears everything down and exits.
  • Several sessions can share one phone: each gets its own server socket and its own mute records.
  • At the end, an app is force-stopped only if headroid started it and nobody else has it open. If it was already running (your copy on the phone, or another session's), only the hidden display is closed and the app keeps running.
  • Android has one UiAutomation (used for the UI tree and setText typing) for every tool on the phone. headroid holds it only while reading the screen and releases it after 5 s idle; when another tool (Appium, uiautomator, Maestro, another session) holds it, a tree read waits up to 10 s, then fails with a message naming the cause.

Use with an AI agent

New in 0.1.0a4. pip install --pre "headroid[artemis]" (Python 3.12+) adds headroid mcp [--serial S], an MCP server for Claude Code, Claude Desktop and other MCP clients. It exposes only mobile_observe, mobile_act and mobile_session_end on the hidden display and needs no LLM API key. Setup, the tool list and the limits (only editing, navigation and printable keys; no open_link yet; Xiaomi needs Battery saver "No restrictions") are in the main README. Do not install it next to the unrelated PyPI package artemis: both use the import name artemis.

Other commands

headroid info | screenshot -o shot.png | tree     # all accept --serial and --jar

--jar is optional everywhere. Resolution order: --jar / jar=, then HEADROID_SERVER_JAR, then the jar bundled in the wheel, then a source-checkout build.

Development

python -m venv .venv && .venv/Scripts/python -m pip install -e ".[dev]" && python -m pytest -q

Tests use a fake JSON-RPC server and a fake adb; no phone needed. To build a wheel with the jar bundled: build the jar, then python scripts/build_wheel.py (needs pip install build).

Metadata

Release files for headroid 0.1.0a4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for headroid 0.1.0a4
File Interpreter ABI Platform Actions
headroid-0.1.0a4-py3-none-any.whl Python 3 none any Details for headroid-0.1.0a4-py3-none-any.whl

Release files / headroid-0.1.0a4-py3-none-any.whl

Download URL headroid-0.1.0a4-py3-none-any.whl
Size 787.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1c4557144da97e28e5fefe1197e4b500ca7f7192c5e37e860135276d2382dd91
BLAKE2b-256 checksum
How to use checksums
fe7b5dd074d74fb25456913495cd524d613d860b8b8e25678da4f6f9e2346473
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for headroid 0.1.0a4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 1 file in this release has been uploaded with provenance metadata.

Attestation for headroid-0.1.0a4-py3-none-any.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page