← all posts

Automating an Android app's UI from a PC with ADB and UI Automator

How I check WhatsApp username availability by driving the app over ADB and reading the UI hierarchy, plus the timing and state problems that make it fiddly.

androidadbautomationpython
Contents
  1. Why ADB and UI Automator
  2. How the loop works
  3. The fiddly parts
  4. Rate limits and being polite
  5. Responsible use

WhatsApp introduced usernames in 2026, and checking whether a particular name is free means opening the edit screen, typing it, and waiting for the app to tell you. Doing that by hand for more than a handful of names gets old fast. So I wrote a small Python script that does the typing and reading for me over ADB. This post is about the technique, because it works for plenty of other "I have to tap through the same screen again and again" problems.

Why ADB and UI Automator

The appeal is how little it needs. The script uses only the Python standard library, plus adb on the PATH and a phone with USB debugging enabled. There is no root, no patched app and no Frida. The phone stays a normal phone; the PC just sends input events and reads what is on screen.

Two pieces of Android tooling do the work:

  • adb shell input sends taps, text and key events as if a finger or keyboard had produced them.
  • uiautomator dump writes the current screen's view hierarchy to an XML file, which you can pull to the PC and parse.

Together they form a loop: act, look, decide.

How the loop works

The flow in my script is roughly this:

  1. Open the username edit screen on the phone by hand.
  2. Dump the hierarchy and find the text field. I look for an EditText node, skip anything that looks like a search box, and tap the centre of its bounds.
  3. Take a baseline dump of the untouched screen, so later I can tell which nodes are new.
  4. For each candidate name: tap the field, clear it with a run of delete key events, then input text the candidate.
  5. Dump the hierarchy again and look for a verdict.

The verdict can show up in two ways. One is a text node saying the name is available or not. The other is the state of the Save button: enabled means the name is free, disabled means it is not. I check both, because a message that is missing or worded differently should not leave me blind.

Here is the generic version of the "dump and find a node" step, with the app-specific parts left out:

import subprocess
import xml.etree.ElementTree as ET

def dump_ui(local="ui.xml"):
    subprocess.run(["adb", "shell", "uiautomator", "dump", "--compressed",
                    "/sdcard/ui.xml"], check=True, capture_output=True)
    subprocess.run(["adb", "pull", "/sdcard/ui.xml", local],
                   check=True, capture_output=True)
    return ET.parse(local).getroot()

def find_by_id(root, rid_part):
    for node in root.iter("node"):
        if rid_part in node.get("resource-id", ""):
            return node
    return None

root = dump_ui()
btn = find_by_id(root, "save_button")
if btn is not None:
    print("enabled:", btn.get("enabled"), "bounds:", btn.get("bounds"))

The bounds attribute looks like [x1,y1][x2,y2]. A small regex gives you the corners, and the midpoint is where you tap.

The fiddly parts

The loop above sounds simple. Most of the real work went into the details.

Timing. The app does not show its verdict instantly, and a dump taken too early shows the old state. My script waits a bit longer before the first check, then polls with shorter pauses until it sees a result or runs out of time. If nothing conclusive appears, it logs the name as "no response" instead of guessing.

Per-command overhead. Starting a new adb process for every tap and keystroke adds noticeable delay on Windows. I keep one adb shell process open and pipe commands into it, then echo a sentinel string so I know when a command has finished. Only adb pull still runs as a separate process.

Screen states. Verdicts can be missing, stale or ambiguous, so the script has a fallback chain: known text first, then the Save button by resource id, then the clickable element under a "Save" label, then any new clickable element near the field compared with the baseline. If the field cannot be found automatically, it asks for tap coordinates once.

Languages and devices. Text matching breaks as soon as the phone is set to another language, so I match a few known English and German phrases and fall back on structure. Resource ids and layouts can also change between app versions. If an update breaks detection, the first thing I try is a longer delay.

The screen has to stay on. The phone must stay awake and unlocked, or the dumps tell you nothing useful.

Rate limits and being polite

The service on the other end is not mine, and I do not know its limits. The project README recommends a delay of about half a second between checks and warns that going lower risks throttling. It also builds in a five-minute rest after every fifteen minutes of work. I would treat those as the floor, not a target. Slower is fine, and so is a short list instead of an endless run.

Responsible use

Automating someone else's app can run against its terms of service, and the repository says so in its disclaimer: your account could be restricted or banned, and you use the tool at your own risk. Read the terms before you try anything like this, and use a device and an account you can afford to lose.

The script only checks whether a name is available. It does not log in as anyone else, and it does not touch other people's data. Keep your own use just as modest: check the names you actually care about, do not hammer the service, and stop if it starts pushing back.

The same pattern of act, dump, parse and decide carries over to any Android screen where you need to repeat a small task. The source is here: https://github.com/Hemiize/Whatsapp-Username-Sniper