Skip to main content

ADB Agent Bridge

Fast, accurate agent→Android control over plain ADB. Nothing to install on the device: the accessibility tree Android already exposes via uiautomator dump gives every element's text, resource-id, content-desc, and bounds — so an AI agent taps element centers instead of guessing pixels from screenshots.

Developed by Kelvin Lee · Apache-2.0

Why this exists

This tool came out of running a real fleet of Android phones driven by AI agents — ContentSwarm, an open phone-agent framework for content automation, with Orphus as the agent harness driving it. The stack we inherited was built the way most phone automation is: for humans watching a screen, not for agents. Two things made it slow and unreliable:

  • The blind pixel loop. Every action meant: capture a 1–2 MB screenshot, send it to a vision model, have the model guess an (x, y) coordinate, then input tap and hope. 2–5 seconds per action, and vision models regress raw pixel coordinates poorly — taps missed, flows derailed, retries compounded.
  • The text dance. Typing one caption meant swapping the IME with four 1-second sleeps (~4s per field), and the on-screen keyboard hid the app's media picker at exactly the wrong moment.

The fix turned out to require almost nothing new. Android already ships the whole answer over plain ADB, on every device, with zero on-device install: uiautomator dump returns the full view hierarchy — the same tree an accessibility service sees — as XML. Parse it host-side, find the element by its text or id, tap its bounds center. Element-center taps can't miss. Text goes through a broadcast to ADBKeyboard in ~100ms, with the IME switched once per session instead of once per field.

The guiding principle throughout: the laziest solution that actually works. No custom accessibility service, no signed APKs, no on-device daemon — until measured latency proves one is needed. Every dump is timed (Bridge.device.last_dump_ms) so that decision is made with data, not vibes.

The three addressing tiers

  1. Semantic (default): find(text="Post") → tap the element's center. Exact, layout-robust, no vision model in the loop.
  2. Set-of-Marks / grid (vision fallback): when the tree is thin (games, canvas, some WebViews), marks() draws numbered boxes on a screenshot — the model picks a number, you tap that element. Or address a 10-column grid cell like "C7".
  3. Raw coordinates: tap((x, y)) still works when you need it.

Install

pip install adb-agent-bridge
# from git: pip install "adb-agent-bridge @ git+https://github.com/kelvincushman/adb-agent-bridge"
# from a checkout: pip install -e .

Requirements:

  • adb on the host PATH, device with USB debugging enabled.
  • ADBKeyboard on the device for fast and unicode text input (recommended). Without it, plain-ASCII text still works via input text.

Setup from zero

Never used adb before? Full path from a factory phone to a working bridge:

  1. Install adb — download platform-tools, extract, and add the folder to your PATH (macOS/Linux: export PATH="$PATH:~/platform-tools"; or brew install android-platform-tools).
  2. Enable USB debugging on the phone — Settings → About phone → tap Build number 7 times to unlock Developer options, then Settings → Developer options → enable USB debugging.
  3. Connect and authorize — plug in via USB, run adb devices, and accept the "Allow USB debugging?" prompt on the phone. The device must list as device (not unauthorized).
  4. Install the bridge and verify
    pip install adb-agent-bridge
    aab ui        # should print the current screen's elements
    
  5. (Recommended) Install ADBKeyboard for ~100ms unicode text:
    curl -LO https://github.com/senzhk/ADBKeyBoard/raw/master/ADBKeyboard.apk
    adb install ADBKeyboard.apk
    adb shell ime enable com.android.adbkeyboard/.AdbIME
    
    The bridge switches to it automatically when typing; restore the normal keyboard afterwards with adb shell ime reset.

With several phones connected, pass -s <serial> to aab (serials come from adb devices) or Bridge("SERIAL") in Python.

Troubleshooting

Symptom Fix
adb: no devices/emulators found Cable/port issue, or USB debugging off (step 2)
Device shows unauthorized Accept the debugging prompt on the phone (step 3)
aab: adb shell failed … Run adb devices — the device dropped or locked
aab ui raises "dump failed twice" Screen mid-animation or canvas-drawn app — retry, or fall back to aab marks / screenshots
Unicode text does nothing ADBKeyboard missing (step 5)
Phone keyboard stuck on "ADB Keyboard" adb shell ime reset

Quick start

from adb_agent_bridge import Bridge

b = Bridge()                      # or Bridge("SERIAL") with multiple devices
b.ui()                            # -> [Element(text=..., id=..., bounds=...), ...]
b.find(text="Post")               # first element whose text matches
b.tap(b.find(text="Post"))        # taps the element's center — can't miss
b.tap((540, 1200))                # raw coordinates
b.tap("C7")                       # grid cell (10 square columns A-J, rows from 1)
b.text("hello world")             # ~100ms, no IME dance
b.text("héllo 👋", clear=True)    # unicode/emoji; clear empties the field first
b.swipe(540, 1600, 540, 400)      # scroll
b.key(66)                         # keyevent (66 = ENTER)
b.screenshot("screen.png")

path, legend = b.marks()          # numbered Set-of-Marks screenshot; the
b.tap(legend[3])                  # vision model picks a number, you tap it

b.prefetch_ui()                   # start the next dump in the background
# ... do other work (e.g. the model decides the next action) ...
b.ui()                            # returns the prefetched result instantly

CLI (installed as aab):

aab ui                    # dump elements, one per line (dump latency on stderr)
aab tap --text Post       # tap by text / --id / --desc
aab tap --grid C7         # or grid cell, or: aab tap 540 1200
aab text "a caption"      # --clear to empty the field first
aab marks annotated.png   # numbered overlay + legend for the vision fallback
aab screenshot out.png
aab swipe 540 1600 540 400
aab key 66                # keyevent (66 = ENTER)
aab -s SERIAL ...         # pick a device when several are connected

After a session, restore the device's normal keyboard with adb shell ime reset (the bridge leaves ADBKeyboard active for speed).

Measured performance

All numbers measured live on a Samsung SM-S721B (Galaxy S24 FE), host on USB:

Operation Cost Notes
tap / swipe / key ~0.1s input is cheap on modern Android
text() via ADBKeyboard ~0.1s any length, any unicode
first text() of a session ~1.5s one-time IME switch + settle wait
screenshot() ~0.8s screencap -p over exec-out
ui()uiautomator dump 2.1–3.0s the bottleneck: fresh uiautomator process per call
ui() on very heavy screens up to ~6s seen on Facebook Marketplace's tree
old vision loop (replaced) 2–5s/action plus missed taps and retries
old IME text dance (replaced) ~4s/field now ~0.1s

Two findings worth knowing:

  • input text costs ~35ms per character (key events are injected one by one), so a 60-char caption takes ~2s. The ADBKeyboard broadcast commits the whole string at once — that's why it's the primary text path.
  • Switching the IME and broadcasting immediately drops the text: the IME hasn't bound to the field yet. One settle wait after the once-per-session switch fixes what the old stack worked around with four sleeps per field.

An agent action cycle (dump → find → tap) is therefore ~2.5s, ~95% of it the dump. Two ways to attack that, both built in:

  • Prefetch (prefetch_ui()): start the next dump right after an action so it overlaps the caller's own work — in an agent loop the model's 1–3s of thinking hides most of the dump for free. Roughly 2× on real flows, no device changes.
  • The optional fast backend below: ~5–8× flat, one APK install per device.

Optional fast backend (fleet phones)

Plain devices need nothing and keep working unchanged. For sub-second action cycles, install openatx/android-uiautomator-server (MIT) on the device and keep its instrumentation running:

adb install app-uiautomator.apk
adb install app-uiautomator-test.apk
adb shell am instrument -w com.github.uiautomator.test/androidx.test.runner.AndroidJUnitRunner

It keeps uiautomator alive and serves the same hierarchy XML over HTTP — Bridge probes for it once per session (adb forward + /ping) and uses it automatically; aab ui reports which backend served the dump, and if the server dies mid-session the bridge falls back to plain dumps. Expected dumps: ~0.1–0.3s instead of 2–3s. Not yet verified against real hardware — the plain-ADB path remains the default and the regression baseline.

Limitations

  • Thin or absent view trees (games, canvas-drawn UIs, some WebViews) — use the Set-of-Marks / grid fallback tier.
  • FLAG_SECURE screens refuse screenshots (banking apps, private modes).
  • uiautomator dump can fail mid-animation; the bridge retries once, then raises so callers can fall back to the vision tier.
  • Unicode text, clear=, and fast typing need ADBKeyboard installed.

Agent integration (Pi / Atomic / Orphus)

An agent-facing skill ships in this repo at skills/adb-agent-bridge/SKILL.md — Pi, Atomic, and Orphus all read the same skill format, so one file covers all three. Install it into your harness's skills directory:

# Orphus                                  # Pi / Atomic
mkdir -p ~/.orphus/agent/skills           mkdir -p ~/.pi/agent/skills
git clone --depth 1 https://github.com/kelvincushman/adb-agent-bridge /tmp/aab-skill
cp -r /tmp/aab-skill/skills/adb-agent-bridge ~/.orphus/agent/skills/   # or ~/.pi/agent/skills/

(Project-level also works: .pi/skills/adb-agent-bridge/ in the repo the agent runs from.)

Or skip the manual steps entirely — paste this setup prompt to any agent with shell access:

Set up adb-agent-bridge so you can control Android phones semantically.

  1. pip install adb-agent-bridge (needs Python 3.9+ and adb on PATH — install Android platform-tools if missing). 2) Run adb devices and get the phone to state device (have me accept the USB-debugging prompt if it says unauthorized). 3) Verify with aab ui — it must print UI elements.
  2. For fast/unicode text, download and adb install ADBKeyboard.apk from github.com/senzhk/ADBKeyBoard, then adb shell ime enable com.android.adbkeyboard/.AdbIME. 5) Install the skill from github.com/kelvincushman/adb-agent-bridge (skills/adb-agent-bridge/) into your skills directory (~/.orphus/agent/skills/ or ~/.pi/agent/skills/). 6) Report back: device serial, element count from aab ui, and whether unicode typing works.

For a full fleet framework built on this bridge (flow learning, replay with run reports, health monitoring, REST API), see ContentSwarm.

Roadmap

  • Fleet-phone verification of the fast backend — the HTTP backend ships mock-tested; its first live run happens on a fleet device, not a personal phone.
  • ContentSwarm integration — element-target taps, layout-robust flow recording with per-replay run reports, a /ui endpoint, and prefetch-during-model-thinking — lives downstream of this library, driven by Orphus agents.

License & credits

Apache-2.0, © 2026 Kelvin Lee. See LICENSE and NOTICE — redistributions must retain the attribution notice.

ADBKeyboard by senzhk inspired the unicode input approach and is driven via its documented broadcast intents. This project contains no ADBKeyboard code.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

adb_agent_bridge-0.1.1.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

adb_agent_bridge-0.1.1-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file adb_agent_bridge-0.1.1.tar.gz.

File metadata

  • Download URL: adb_agent_bridge-0.1.1.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for adb_agent_bridge-0.1.1.tar.gz
Algorithm Hash digest
SHA256 8f458076f1ed8d65c50e49b302e8df638401cc93abaa1ec2d84c5507ea71e391
MD5 d3299cf67a9a30a254c40961d47e756e
BLAKE2b-256 1b1a8b18980fecdd3291203f809e67c6557313179fbcfddfddbe7db9ce72087d

See more details on using hashes here.

File details

Details for the file adb_agent_bridge-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: adb_agent_bridge-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 19.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for adb_agent_bridge-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dc47bccf428f6605251576408ea34eb8d97c3b3f5752d6030dd641cc21ea7f57
MD5 9f1848c52b78ebc8ccfe8d4e464b08c8
BLAKE2b-256 a04d2ee88073bd87172bff983c83ac9d2a85aeef50b61dc8dd694c8363c02e4e

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.2

2 files

This release

0.1.1 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page