Generic Android bridge for any app. Reads screens via OCR, executes taps/scrolling/typing.
Other skills depend on this one. It ships the shared parts:
FileRoleocr_bridge.pyThe OCR loop — screenshot → RapidOCR → click/swipe → re-verify. This is what dependents use.requirements-adb.txtPinned deps for the OCR-loop venv. Dependents install it as $BASE/requirements-adb.txt.bridge_daemon.pyAppium backend (fallback only; see the failure mode below)start_bridge.shLaunches the Appium daemon (fallback only)
Dependents: 12306-android-adb, pdd-android-adb, hospital-android-adb. Each documents
$PY $BASE/ocr_bridge.py <cmd> where BASE is this skill's directory, so it works from any working
directory.
ClawHub has no dependency resolver — installing a dependent skill does not install this one. Install it explicitly:
bash
clawhub install appium-android-adb
ocr_bridge.py imports no Appium (only uiautomator2 + rapidocr-onnxruntime), so dependents
pay nothing for the Appium backend they never use. Note the name under-sells the skill: the OCR loop
is the reason it exists and the reason others depend on it.
Two backends:
BackendStatusBest forLimitationsuiautomator2 + RapidOCR ( ocr_bridge.py)✅ Primary (proven end-to-end on 12306 booking 2026-08-30)UC WebView apps, scrolling, booking buttons, Chinese inputNeeds rapidocr_onnxruntime in a venvAppium ( bridge_daemon.py)⚠️ FallbackNative apps, structured dumps, find/ waitDead on many consumer devices: Realme etc. block WRITE_SECURE_SETTINGS, so io.appium.settings and IME switching fail → session never starts
On a Realme RMX3350 the Appium path was unusable:
WRITE_SECURE_SETTINGS → io.appium.settings commands faild.set_input_ime() blocked → fastinput IME can't be activated → send_keys failsRule: try the OCR loop FIRST. Only fall back to Appium if the app is fully native and OCR is insufficient.
bash
# One-time venv (Python 3.10+), shared with the three dependent skills.
# The pins live in requirements-adb.txt, in this skill's directory; adjust BASE
# if you installed the skill somewhere other than the default.
# Pinned deliberately — an unpinned `pip install` resolves "latest" at install
# time, so the code that actually runs is whatever PyPI happened to serve that
# day and can change under you (ClawHub review [T08]).
BASE=~/.openclaw/workspace/skills/appium-android-adb # this skill
python3 -m venv ~/.openclaw/workspace/.venv-adb
~/.openclaw/workspace/.venv-adb/bin/pip install -r $BASE/requirements-adb.txt
# Every interaction is one command:
PY=~/.openclaw/workspace/.venv-adb/bin/python
$PY ocr_bridge.py dump # OCR whole screen: (text, l, t, r, b)
$PY ocr_bridge.py markers # page-state markers
$PY ocr_bridge.py tap-text 预订 --idx 0 # click OCR match, then re-OCR verify
$PY ocr_bridge.py tap 540 1987 # click exact coordinates
$PY ocr_bridge.py swipe up --scale 0.35 # normal list scroll
$PY ocr_bridge.py jump --n 3 # 3 rapid swipes (unstuck virtual list)
$PY ocr_bridge.py wait-text 提交订单 # poll until text appears
uiautomator2 auto-installs its atx-agent on the phone at first connect() — no Appium needed.
The bare $PY ocr_bridge.py … form above works here because you're in this skill's directory.
Dependent skills invoke it as $PY $BASE/ocr_bridge.py … instead — $BASE being this skill's
path — so their commands run from any working directory. Use $BASE in anything you write that has
to work outside this directory.
shot(): adb exec-out screencap -p (streamed over USB — nothing is written to /sdcard) → RapidOCR → [(text, l, t, r, b)]. The screencap + pull pair survives only as a fallback for old platform-tools, and deletes its device-side file.
Historical note: this step used to leave
s.pngin/sdcardforever, outside the private 0700 screenshot dir the Safety section promises. Fixed in 1.3.5 — see ClawHub review finding[T09].
Decide: page-state via markers(), position via deps() (HH:MM at x<250)
Act: d.click(cx, cy) / d.swipe_ext('up', scale=0.35) / d.swipe(x1,y1,x2,y2,duration=0.4)
Always re-OCR after every action and verify the screen actually changed — this is what makes the loop robust against the virtual list's lies.
IME switching is blocked on locked-down devices. The proven route:
python
d.set_clipboard('苏州') # uiautomator2 clipboard (works where fastinput fails)
d.long_click(400, 330, duration=1.2) # long-press the input field
# → OCR: the 粘贴 (paste) menu item appears → tap it
ocr_bridge.py type 苏州 --x 400 --y 330 sets clipboard and long-presses the field.
Clear the field first — pasting over existing text concatenates instead of
replacing (hit on the PDD search box). ocr_bridge.py type clears any focused
EditText by default ( --no-clear to skip).
Payment pages (e.g. PDD 多多钱包, PayConfirmActivity) set FLAG_SECURE:
screencap returns black/0-byte files. The accessibility tree still works —
read and tap via it:
bash
$PY ocr_bridge.py a11y # all text nodes + bounds + [SELECTED] flag
$PY ocr_bridge.py a11y-selected # nodes with selected="true" (spec chips)
$PY ocr_bridge.py a11y-tap 直接免拼 # click text found in the a11y tree
Also the authoritative state check for selection chips when OCR is ambiguous:
selected="true" in the tree beats pixel guessing.
d.swipe_ext('up', scale=0.35) (≈2 train cards per scroll on 12306)jump — 3 rapid large swipes ( scale=0.5, 1.2s apart), then micro-swipes back ( scale=0.15–0.25, or d.swipe(540, 1900, 540, 800, duration=0.4))deps() = HH:MM texts at x<250 — you always know which time window is on screen, independent of the DOMbash
# One-shot commands run with a short-lived session by default (safer):
python3 bridge_daemon.py dump
python3 bridge_daemon.py tap '{"text": "查询车票"}'
python3 bridge_daemon.py scroll '{"direction": "down"}'
# Persistent mode is opt-in — only if you need low-latency batches:
bash ~/.openclaw/workspace/skills/appium-android-adb/start_bridge.sh
# ...which runs: python3 bridge_daemon.py --daemon
A background daemon is NOT auto-started by single commands; one-shot mode
creates a session, executes, and quits. Screenshots and IPC files live in the
private 0700 dir ~/.cache/appium-bridge. Cannot run simultaneously with
uiautomator2 (same AccessibilityService).
Appium binds to loopback only. Both launch paths pass --address 127.0.0.1,
because Appium exposes full control of the attached device — without it the
server binds broadly and anything else on the LAN could drive the phone. If you
start Appium yourself, do the same:
bash
appium --address 127.0.0.1
All from ~/.openclaw/workspace/skills/appium-android-adb/. All return JSON.
bash
python3 bridge_daemon.py dump
Returns:
json
{
"ok": true,
"package": "com.MobileTicket",
"activity": "com.alipay.mobile.nebulacore.ui.H5Activity",
"title": "上海 <> 苏州",
"alerts": [{"text": "温馨提示"}],
"buttons": [
{"text": "查询车票", "id": "", "bounds": "[99,970][981,1102]", "x": 540, "y": 1036},
{"text": "预订", "id": "", "bounds": "[870,1988][1008,2075]", "x": 939, "y": 2031}
],
"trains": [
{"text": "G 7 0 0 4次列车...", "bounds": "[66,1793][291,1919]", "clickable": true, "x": 178, "y": 1856}
],
"webview_contexts": ["NATIVE_APP"]
}
bash
python3 bridge_daemon.py tap '{"text": "查询车票"}'
python3 bridge_daemon.py tap '{"text": "预订", "index": 0}'
python3 bridge_daemon.py tap '{"id": "btn_submit"}'
bash
python3 bridge_daemon.py tap_bounds '{"bounds": "[870,1988][1008,2075]"}'
⚠️ Only works reliably if the element has proper bounds (h > 20px). Collapsed virtual list items (h=6px) will NOT respond.
bash
python3 bridge_daemon.py tap_coords '{"x": 540, "y": 1200}'
bash
python3 bridge_daemon.py find '{"text": "7004"}'
bash
python3 bridge_daemon.py scroll '{"direction": "down"}'
python3 bridge_daemon.py scroll '{"direction": "down", "distance": "micro"}'
⚠️ May not work on UC WebView (used by 12306, Alipay apps). The WebView filters touch events. For UC WebView scrolling, use the OCR loop / d.swipe_ext() instead.
bash
python3 bridge_daemon.py screenshot
# Returns {"ok": true, "path": "/tmp/screen.png"}
bash
python3 bridge_daemon.py type '{"text": "上海"}'
bash
python3 bridge_daemon.py wait '{"text": "提交订单", "timeout": 30}'
SituationRecommended ToolUC WebView apps (12306, Alipay, many Chinese apps) — everythingOCR loop ( ocr_bridge.py)Screen state / text readingocr_bridge.py dump / markers / depsClicking anything visibleocr_bridge.py tap-text / tap (u2 click)Scrolling UC WebView listsocr_bridge.py swipe / jump / microChinese text inputd.set_clipboard + long-press pasteNative apps with structured dumps / find / waitbridge_daemon.py (Appium) — if the device allows it
Apps using UC WebView (12306, many Chinese apps) use virtual lists that collapse most items' accessibility bounds:
d.click(x, y) from uiautomator2 — reliably delivers touches (unlike raw ADB)d.swipe_ext() / d.swipe()d.set_clipboard() + long-press paste for Chinese inputd.dump_hierarchy() sometimes gives accurate data — use when it renders, never depend on itadb shell input tap — filtered by UC WebView (observed 2026-08-30)mobile: scrollGesture via Appium — returns Falseexecute_script) — no WEBVIEW contexts exposedd.set_input_ime() / fastinput on Realme — WRITE_SECURE_SETTINGS blocked~/.cache/ocr-bridge, override
OCR_BRIDGE_DIR) and shots older than 1 hour are purged automatically on
every screenshot; ocr_bridge.py clean purges immediately. Note that
clean only ages out that host-side dir — it cannot reach the device, so
nothing is written to /sdcard in the first place: captures stream over
USB via adb exec-out, and the older screencap + pull fallback deletes
its own device file.127.0.0.1 on every launch path.
It offers unrestricted control of the connected device, so never start it on
0.0.0.0, and stop it when you're done.bridge_daemon.py keeps its command/response files in a
private 0700 dir ( ~/.cache/appium-bridge) and rejects files not owned by
the current user. Appium runs WITHOUT --allow-insecure/ --relaxed-security.Appium session creation error / "settings command problem": Device blocks WRITE_SECURE_SETTINGS (Realme etc.). Stop fighting it — use the OCR loop.
uiautomator2 conflict: "AccessibilityService already registered" means Appium is running. Kill Appium first ( pkill -f appium).
OCR finds nothing / misreads: RapidOCR is noisy on small text — narrow search to a region (x/y bounds) and re-shoot; the screen may still be animating (sleep 2-3s after actions).
Train number not found: DOM text has spaces ( G 7 0 0 4), but OCR text does not ( G7004). Match with regex on OCR: ^[GDCK]\d+.
tesseract missing: Don't install it — RapidOCR (rapidocr-onnxruntime) needs no language data and read Chinese reliably in production.