adbutils

repository·master·Indexed 21 days ago

https://github.com/openatx/adbutils

A Python library providing a high-level interface for the Android Debug Bridge (ADB) service. It enables device management, shell command execution, file transfers via sync, input simulation, and screen capture. Features include support for ADB port forwarding/reversing, system property access, app management, and a CLI for interacting with connected Android devices.

Tokens
4.7K
Snippets
22
Records
23
Agent score
27%

What's inside adbutils

  1. Capture ADB traffic with Charles Proxy

    master

    You can intercept and inspect ADB protocol traffic using Charles Proxy by setting up port forwarding. This is useful for debugging the communication between the host and the ADB server.

    Setup Steps:

    1. In Charles, go to Proxy -> Port Forwarding.
    2. Add a rule to map a local TCP port to the remote ADB server port:
      • Local: 5555 (or any available port)
      • Remote: localhost:5037 (the default ADB server port)
    3. Execute ADB commands using the specified port via the -P flag:
      adb -P 5555 devices
    4. The traffic should now appear in Charles.
    adb -P 5555 devices
  2. Migration guide from 1.x to 2.x

    master

    If you are upgrading from version 1.x to 2.x, note the following breaking changes:

    Removed:

    • current_app (Use app_current instead)
    • package_info (Use app_info instead)

    Added:

    • volume_up
    • volume_down
    • volume_mute
  3. Configure adbutils via environment variables

    master

    You can control the behavior of adbutils and its connection logic using the following environment variables:

    • ADBUTILS_ADB_PATH: Path to the adb binary (defaults to searching the system PATH).
    • ANDROID_SERIAL: The serial number of the device to connect to.
    • ANDROID_ADB_SERVER_HOST: The host address of the ADB server (defaults to 127.0.0.1).
    • ANDROID_ADB_SERVER_PORT: The port of the ADB server (defaults to 5037).

    Docker Note: If running in a Docker environment, you may need to set ANDROID_ADB_SERVER_HOST=host.docker.internal to reach the host's ADB server.

    export ANDROID_ADB_SERVER_HOST=host.docker.internal
  4. Read Logcat stream in Python

    master

    To read logcat output programmatically, use d.shell("logcat", stream=True) and iterate over the stream connection.

    d.shell("logcat --clear")
    stream = d.shell("logcat", stream=True)
    with stream:
        f = stream.conn.makefile()
        for _ in range(100): # read 100 lines
            line = f.readline()
            print("Logcat:", line.rstrip())
        f.close()
  5. Record video using screenrecord

    master

    You can manually control the start and stop of a screen recording using the screenrecord method on a device object.

    # Manual control approach
    r = d.screenrecord(no_autostart=True)
    r.start() # start record
    r.stop_and_pull("video.mp4") # stop and pull to local
    
    # Alternative: shell approach
    import time
    stream = d.shell("screenrecord /sdcard/s.mp4", stream=True)
    time.sleep(3) # record for 3 seconds
    with stream:
        stream.send(b"\003") # send Ctrl+C
        stream.read_until_close()
    d.sync.pull("/sdcard/s.mp4", "s.mp4")
  6. Run shell commands

    master

    Execute shell commands on the device using several methods:

    • d.shell(cmd): Standard shell execution. Supports str or list[str] arguments. Can take a timeout.
    • d.shell2(cmd): Advanced shell that returns a ShellReturn object containing args, returncode, stdout, and stderr. Setting v2=True enables the shell v2 protocol for better stream handling.
    • d.open_shell(cmd): Opens a persistent connection (AdbConnection) to a command, allowing manual send() and recv().
    • d.prop: Access system properties via d.prop.get(key) or direct attribute access (e.g., d.prop.model).
    from adbutils import adb
    
    d = adb.device()
    
    # Basic shell
    serial = d.shell(["getprop", "ro.serial"])
    serial = d.shell("getprop ro.serial")
    d.shell("sleep 1", timeout=0.5) # Raises AdbTimeout if exceeded
    
    # Advanced shell (ShellReturn)
    ret = d.shell2("echo 1")
    print(ret.returncode, ret.output)
    
    # Shell v2 protocol
    ret = d.shell2("echo 1; echo 2 1>&2", v2=True)
    print(ret.stdout, ret.stderr)
    
    # Persistent shell connection
    c = d.open_shell('cat')
    c.send(b'hello\n')
    print(c.recv(100))
    c.close()
    
    # System properties
    model = d.prop.get("ro.product.model")
    # Use cache for speed
    model_cached = d.prop.get("ro.product.model", cache=True)
    d = adb.device()
    
    # Basic shell
    serial = d.shell(["getprop", "ro.serial"])
    d.shell("sleep 1", timeout=0.5)
    
    # Advanced shell
    ret = d.shell2("echo 1")
    print(ret.returncode, ret.output)
    
    # Shell v2
    ret = d.shell2("echo 1; echo 2 1>&2", v2=True)
    print(ret.stdout, ret.stderr)
    
    # Persistent connection
    c = d.open_shell('cat')
    c.send(b'hello\n')
    print(c.recv(100))
    c.close()
  7. Connect and disconnect remote devices

    master

    Manage remote connections (equivalent to adb connect and adb disconnect).

    from adbutils import adb, AdbTimeout, AdbError
    
    # Connect to a remote device
    output = adb.connect("127.0.0.1:5555")
    
    # Connect with a specific timeout
    try:
        adb.connect("127.0.0.1:5555", timeout=3.0)
    except AdbTimeout as e:
        print(e)
    
    # Disconnect
    adb.disconnect("127.0.0.1:5555")
    
    # Disconnect and raise error if device is not present
    adb.disconnect("127.0.0.1:5555", raise_error=True)
    
    # Wait for device state
    adb.wait_for("127.0.0.1:5555", state="device")      # Wait for online
    adb.wait_for("127.0.0.1:5555", state="disconnect") # Wait for disconnect
    from adbutils import adb
    
    output = adb.connect("127.0.0.1:5555")
    try:
        adb.connect("127.0.0.1:5555", timeout=3.0)
    except AdbTimeout as e:
        print(e)
    
    adb.disconnect("127.0.0.1:5555")
    adb.wait_for("127.0.0.1:5555", state="device")
  8. Use extended device functions

    master

    adbutils provides high-level wrappers for common automation tasks.

    App Management

    • d.app_info(package_name): Returns info like version_name and version_code.
    • d.app_current(): Returns the currently focused app (package, activity, pid).
    • d.install(path_or_url): Installs an APK from a local path or URL.
    • d.list_packages(): Returns a list of installed packages.

    Input Simulation

    • d.click(x, y): Click at coordinates. Supports normalized floats (0.0 to 1.0) for center screen.
    • d.swipe(x1, y1, x2, y2, duration): Swipe between points.
    • d.drag(x1, y1, x2, y2, duration): Drag and drop.
    • d.send_keys(text): Simulate text input.
    • d.keyevent(name): Simulate hardware keys (e.g., HOME).
    • d.volume_up(times=1), d.volume_down(), d.volume_mute(): Volume control.

    Device Control

    • d.window_size(landscape=False): Get screen dimensions.
    • d.rotation(): Get current rotation (0-3).
    • d.brightness_value / d.brightness_value = val: Get/set brightness (0-255).
    • d.brightness_mode: Get/set BrightnessMode.AUTO or BrightnessMode.MANUAL.
    • d.battery(): Get BatteryInfo object.
    • d.is_screen_on(): Check if screen is active.
    • d.root(): Run adb root.
    • d.tcpip(port): Set device to listen on TCP port.
    # App info
    info = d.app_info("com.example.demo")
    
    # Input
    d.click(0.5, 0.5) # Click center
    d.swipe(10, 10, 200, 200, 0.5)
    d.send_keys("hello world")
    
    # System
    print(d.battery().level)
    d.brightness_value = 150
    d.brightness_mode = BrightnessMode.MANUAL
    d.install("apidemo.apk")
    d.click(0.5, 0.5)
    d.swipe(10, 10, 200, 200, 0.5)
    d.app_current()
    d.keyevent("HOME")
    d.brightness_value = 100
  9. Connect to the ADB Server

    master

    Use AdbClient to manage connections to the ADB server. You can specify the host, port, and socket_timeout.

    By default, you can use the shorthand from adbutils import adb to get a pre-configured client.

    import adbutils
    
    # Explicit client configuration
    adb = adbutils.AdbClient(host="127.0.0.1", port=5037, socket_timeout=10)
    
    # List all devices with extended info (serial, state, transport_id)
    for info in adb.list(extended=True):
        print(info.serial, info.state, info.transport_id)
    
    # List only devices with state='device'
    print(adb.device_list())
    import adbutils
    
    # Explicit client configuration
    adb = adbutils.AdbClient(host="127.0.0.1", port=5037, socket_timeout=10)
    for info in adb.list(extended=True):
        print(info.serial, info.state)
    
    # only list state=device
    print(adb.device_list())
  10. Get a device object

    master

    Retrieve a device instance using adb.device(). You can identify a device by its serial or transport_id.

    If only one device is connected, you can call adb.device() without arguments. Note that this will raise a RuntimeError if multiple devices are connected.

    from adbutils import adb
    
    # Get device by serial
    d = adb.device(serial="33ff22xx")
    
    # Get device by transport_id (found via 'adb devices -l')
    d = adb.device(transport_id=24)
    
    # Get the only connected device
    d = adb.device()
    from adbutils import adb
    
    d = adb.device(serial="33ff22xx")
    # or
    d = adb.device(transport_id=24)
    # or
    d = adb.device()
  11. Transfer files using sync

    master

    Use the d.sync interface to push and pull files or data.

    Push

    • d.sync.push(data, remote_path): Supports binary strings, io.BytesIO objects, local file paths, or pathlib.Path objects.

    Pull and Read

    • d.sync.pull(remote_path, local_path): Copies a file from device to local.
    • d.sync.read_text(remote_path, encoding): Reads file content as text.
    • d.sync.read_bytes(remote_path): Reads file content as bytes.
    • d.sync.iter_content(remote_path): Iterates over file chunks.
    from adbutils import adb
    import io
    import pathlib
    
    d = adb.device()
    
    # Push
    d.sync.push(b"Hello Android", "/data/local/tmp/hi.txt")
    d.sync.push(io.BytesIO(b"Hello Android"), "/data/local/tmp/hi.txt")
    d.sync.push("/tmp/hi.txt", "/data/local/tmp/hi.txt")
    
    # Read
    text = d.sync.read_text("/data/local/tmp/hi.txt", encoding="utf-8")
    
    # Pull
    d.sync.pull("/data/local/tmp/hi.txt", "hi.txt")
    d.sync.push(b"Hello Android", "/data/local/tmp/hi.txt")
    d.sync.push("/tmp/hi.txt", "/data/local/tmp/hi.txt")
    
    output = d.sync.read_text("/data/local/tmp/hi.txt", encoding="utf-8")
    d.sync.pull("/data/local/tmp/hi.txt", "hi.txt")