Dump+parse UI hierarchy, tap/type/swipe, handle MIUI security restrictions, read SMS from inbox, open incognito tabs. Use when the user wants to automate, control, or drive an Android phone/tablet via USB (form filling, app flows, SMS codes, signup flows), or when adb shell input fails with SecurityException/INJECT_EVENTS on MIUI/Xiaomi devices.
Install
npx skillscat add youlianvr/oper-share/android-adb-automation Install via the SkillsCat registry.
Android ADB Automation
Playbook for driving an Android phone from the PC over USB ADB.
Historical recipe reported on a Redmi 9T (MIUI 14) in 2026-08; no current
device is connected or operation is live-verified in this workspace.
Reusable as a procedure template for phone UI automation, subject to a fresh
device-specific probe and explicit authorization for mutations.
1. Basics
export MSYS_NO_PATHCONV=1
ADB="~/workspace/AppData/Local/Android/Sdk/platform-tools/adb.exe" # configured path; verify existence before use
S="<serial>" # adb devices — show serials
$ADB -s $S shell dumpsys window | grep mCurrentFocus # what's on screen
$ADB -s $S shell svc power stayon true # keep screen on
export MSYS_NO_PATHCONV=1— REQUIRED on Git Bash/Windows, else paths get mangled.MIUI pull trap:
adb pull /sdcard/ui.xml ui.xmlfrom the project root mangles the destpath.
cdinto the target dir FIRST, then pull with a relative name.MIUI dumps print a warning to stderr ("Broadcast queue..." / "error: buffer overflow") —
harmless, ignore it.
2. Dump + parse UI (the core loop)
$ADB -s $S shell uiautomator dump /sdcard/ui.xml >/dev/null 2>&1
cd <target_dir> && $ADB -s $S pull /sdcard/ui.xml ui.xml >/dev/null 2>&1
Parse the XML for actionable nodes. A stable helper exists:
_scripts/phone-tools/phone_ui.py — run it with the serial, it dumps + prints
text/class/content-desc/bounds/clickable rows (webview-inclusive):
python _scripts/phone-tools/phone_ui.py <serial> [outfile.xml]
Fallback one-liner (Windows console-safe: write to file, don't print UTF-8 to console):
# python -c "..." → regex over the xml file, print text + bounds for non-empty nodes
import re; xml=open('ui.xml',encoding='utf-8').read()
for m in re.finditer(r'<node[^>]*?text="([^"]*)"[^>]*?class="([^"]*)"[^>]*?bounds="(\[[^"]+\])"', xml):
t,c,b=m.groups()
if t: print(repr(t), c.split('.')[-1], b)
Finding tap coordinates: for a node with bounds [x1,y1][x2,y2], tap center =
((x1+x2)//2, (y1+y2)//2). Compute the center yourself; bash arithmetic on the two
bracketed pairs often breaks — do it in Python or hardcode the center.
3. Input injection (device mutation; explicit authorization required)
$ADB -s $S shell input tap X Y
$ADB -s $S shell input text 'Hello%sspace' # %s = space
$ADB -s $S shell input keyevent 111 # ESC — hide keyboard
$ADB -s $S shell input keyevent 61 # TAB
$ADB -s $S shell input keyevent 4 # BACK
$ADB -s $S shell input swipe X1 Y1 X2 Y2 300 # swipe (notifications shade)
⚠️ MIUI blocker — INJECT_EVENTS / WRITE_SECURE_SETTINGS denied
On Xiaomi/MIUI adb shell input throws SecurityException and settings put throws
WRITE_SECURE_SETTINGS deny until the user enables:
Settings → Advanced Settings → Developer Options → "USB Debugging (Security Settings)".
This single toggle unlocks BOTH input injection AND settings writes.
Until it's on: taps silently no-op (or throw),
settings put global mobile_data 1is denied.Ask the owner to flip it — there is NO software workaround.
4. Reading SMS (device data; explicit authorization and privacy review required)
$ADB -s $S shell content query --uri content://sms/inbox --projection address,body,date
Filter for the sender you need (e.g. Google) and take the newest body containing
G-XXXXXX. The code arrives on the physical SIM; the app/URL you're verifying stays
live on screen — read the inbox, extract the code, type it into the form.
5. Forms — the real-world pitfalls
Autofill sabotage: Chrome autofill injects junk into fields (typed text lands wrong,
suggestions overlay the field). Fix: tap exactly the field center, clear via
select-all+delete (
keyevent 29etc. is unreliable — clear manually), retype.Keyboard covers the button: buttons get bounds
[0,0][0,0]while the keyboard is up.Hide keyboard (keyevent 111 / 4), re-dump, THEN tap the button at its now-visible center.
Spinners (month, gender): tap the spinner, the option list opens; pick the option by
center coords from the fresh dump. Re-dump after every open.
Session expiry: web signup flows die after long idle ("Session expired / retry").
Just re-fill from the first step — don't fight it.
Password confirm field: often covered by the autofill overlay; clear field 1 fully,
then fill both fields with precise taps after re-layout.
6. Incognito on mobile Chrome
Programmatic incognito (intents, CDP
Target.createBrowserContext) FAILS on mobileChrome — all return
ctx=None.Only path: tap the tab-switcher button (top-right,
tab_switcher_button), then"New Incognito Tab" in the switcher UI. This is a USER-tap flow via input.
Use incognito for anything that must NOT touch the owner's logged-in profile (signups,
testing) — the normal profile has the owner's accounts.
7. Hygiene
Do not run
svc power stayon trueautomatically: it changes device state. Use only with explicit owner authorization.Don't reboot or otherwise mutate the owner's router/phone without explicit permission; network/device changes are outside read-only verification.
Don't touch the owner's normal Chrome profile — use incognito for experiments.
8. Reference implementation
Full working example of a phone-driven Google signup (every step above, 40+ taps):
projects/gmail-unlimited/ACCOUNT_userktf8mdw.md (recipe section).
Tooling home: _scripts/phone-tools/ (README inside). Canon:
knowledge/findings/2026-08-03-A2-mission-experience.md §3-4.