EngineeringApril 22, 2026Updated: April 24, 20269 min read

Building an Android Phone Farm Agent Loop with ADB & UiAutomator2

A practical guide to architecting a multi-device Android automation system with UiAutomator2, OCR, and a persistent agent loop.

L

Lugon

Vibe Engineer

Share article
Building an Android Phone Farm Agent Loop with ADB & UiAutomator2

Why a Phone Farm Needs an Agent Loop

Running tasks across 10–50 Android devices manually is chaos. Apps crash, sessions expire, CAPTCHAs appear. A naive script-per-device approach collapses. What you need is a persistent agent loop per device.


Architecture

Task Scheduler → Device Manager → Agent Loop (UiAutomator2 + OCR) → scrcpy monitor

1. Device Discovery

def list_devices():
    result = subprocess.run(["adb", "devices", "-l"], capture_output=True, text=True)
    devices = []
    for line in result.stdout.strip().splitlines()[1:]:
        if not line.strip(): continue
        parts = line.split()
        serial, state = parts[0], parts[1]
        devices.append(Device(serial=serial, state=state))
    return [d for d in devices if d.state == "device"]

2. Agent Loop

async def agent_loop(serial: str, task_queue: asyncio.Queue):
    d = u2.connect(serial)
    while True:
        task = await task_queue.get()
        try:
            await execute_task(d, task)
        except Exception:
            await handle_recovery(d, serial)
        finally:
            task_queue.task_done()

3. OCR Fallback

async def ocr_tap(d, target_text: str):
    img = d.screenshot(format="opencv")
    result = ocr.ocr(img, cls=True)
    for line in result[0]:
        bbox, (text, conf) = line
        if target_text.lower() in text.lower() and conf > 0.8:
            cx = int((bbox[0][0] + bbox[2][0]) / 2)
            cy = int((bbox[0][1] + bbox[2][1]) / 2)
            d.click(cx, cy)
            return

4. Recovery

async def handle_recovery(d, serial):
    for fn in [lambda: d.press("back"), lambda: d.press("home"), lambda: d.reboot()]:
        try:
            fn()
            await asyncio.sleep(2)
            if is_responsive(d): return
        except: continue

Key Takeaways

  • One agent loop per device — never share UiAutomator2 across threads
  • OCR fallback for canvas-rendered UIs
  • Build recovery path before the happy path
  • scrcpy on a separate channel from automation
androidadbuiautomator2pythonautomation
Share article
Start Your Project

Ready to transform?

Discover how TeguFy can help your business simplify, amplify, and fortify with AI, Blockchain, and cutting-edge technology.