adam

repository·master·Indexed 19 days ago

https://github.com/malinskiy/adam

A Kotlin-based Android Debug Bridge (ADB) helper library designed as a coroutine-powered, resource-efficient alternative to ddmlib. It provides comprehensive support for shell operations, package management (including atomic multi-package and split APK installation), device monitoring, file synchronization (push/pull), emulator control, logcat, and port forwarding. Currently in pre-v1.0.0 development.

Tokens
23.6K
Snippets
93
Records
107
Agent score
66%

What's inside adam

  1. Overview of adam

    master
    adam is an Android Debug Bridge (ADB) helper library written in Kotlin. It is designed as a more efficient alternative to ddmlib, specifically optimized for resource usage by utilizing Kotlin Coroutines instead of blocking threads. This makes it particularly suitable for scenarios involving communication with dozens of connected devices simultaneously.
  2. Choose an Image adapter for screen capture

    master

    When capturing screenshots, you must use an adapter to transform framebuffer bytes into a usable format. There are two primary options:

    1. RawImageScreenCaptureAdapter: The bare minimum adapter. It returns a RawImage object containing metadata and a byte[]. You can retrieve pixel values using RawImage#getARGB(index: Int) or transform it into a Java BufferedImage using .toBufferedImage().
    2. BufferedImageScreenCaptureAdapter: Recommended if you are capturing many screenshots for a single device. It is optimized to reduce memory allocations during the transformation process.

    Thread Safety and Memory Reuse

    By default, all adapters attempt to reduce memory consumption by reusing internal buffers. Do not use the same adapter instance across multiple threads in parallel. If you must use multiple threads, either:

    • Set the buffer to null every time,
    • Or provide an external buffer that is allocated per thread.
  3. API Stability Warning

    master
    Note that adam is currently in pre-v1.0.0 development. There is no guarantee that interfaces and request structures will remain stable between versions. Use with caution in production environments until a stable release is provided.
  4. Understand File Request types in Adam

    master

    Adam uses specific request types to transfer files or information about files between devices. Depending on your requirements, you should choose one of the following approaches:

    • Recommended Requests: Use these if you want to push or pull files without needing to manage device-specific features or underlying protocols manually.
    • Compatibility Requests: Use these when working with sync v1 or v2 protocols where you need to implement fallbacks for devices that do not support certain features.
    • Sync v1/v2 Direct Interaction: For low-level control, you can interact directly with the sync v1 or sync v2 protocols.
    • Plain ls (ListFilesRequest): For maximum compatibility, you can use the ls command, which is wrapped in the ListFilesRequest abstraction.
  5. Handle different response types in adam

    master

    Requests in adam return one of two types of responses depending on the request type:

    1. Single Response: For requests that return a discrete result (e.g., ListDevicesRequest), adbClient.execute() returns the result directly.
    2. Stream of Responses: For requests that involve ongoing progress (e.g., PullFileRequest), adbClient.execute() returns a ReceiveChannel. You can iterate over this channel to monitor progress or wait for completion.

    Note: When using streaming requests, you must provide a CoroutineScope (e.g., GlobalScope) and a serial if targeting a specific device.

    // Single response example
    val devices: List<Device> = adbClient.execute(request = ListDevicesRequest())
    
    // Stream of responses example
    val channel = adbClient.execute(
        request = PullFileRequest("/data/local/tmp/testfile", testFile),
        scope = GlobalScope,
        serial = "emulator-5554"
    )
    
    while (!channel.isClosedForReceive) {
        val progressDouble = channel.receiveOrNull() ?: break
        println(progressDouble)
    }
  6. Specify request targets (Host, Serial, USB, Local)

    master

    When executing a request, you can specify a target to tell the ADB server which device or entity the request applies to. If you do not specify a target, sensible defaults are used (e.g., KillAdbRequest defaults to HostTarget).

    Available Targets

    • HostTarget: Refers to the host or 'any single device/emulator' connected to the host.
    • SerialTarget: Targets a specific device using a serial number (often via a host-serial:<serial-number>: prefix).
    • UsbTarget: Targets the single USB device connected to the host. Fails if zero or multiple USB devices are present.
    • LocalTarget: Targets the single emulator instance running on the host. Fails if zero or multiple emulators are present.
    • NonSpecifiedTarget: No specific target is provided.

    Targeting a specific device

    For requests that must act on a specific device (like ScreenCaptureRequest), you must provide the serial parameter in the execute call. You can obtain device serials by running ListDevicesRequest or AsyncDeviceMonitorRequest.

    // Targeting a specific device by serial
    adb.execute(
        request = ScreenCaptureRequest(),
        serial = "emulator-5554"
    )
  7. Supported ADB functionalities in adam

    master

    adam provides a comprehensive suite of ADB-related capabilities, including:

    • Shell Operations: Support for shell:, shell_v2 (with separated stdout, stderr, and exit code), and legacy exec shell with stdin.
    • Package Management: Streaming installation, atomic multi-package installation, APK split installation, APEX support, sideloading, and install sessions.
    • Device Management: Listing and continuous monitoring of connected devices, fetching device features, connecting/disconnecting/reconnecting, WiFi pairing, and device reboots.
    • File Operations: ls, recursive push/pull, and sync: (supporting stat_v2, sendrecv_v2, and ls_v2).
    • Emulator Control: Commands like gsm call and rotate.
    • System Properties: Fetching single or all system properties.
    • Instrumented Tests: Parsing raw and proto output.
    • Screen Capture: Dynamic adapters with raw buffers and fast BufferedImage conversion, supporting sRGB and DCI-P3.
    • Logcat: Fetching logs and continuous monitoring.
    • Port Forwarding: Managing port-forwarding and reverse port-forwarding rules.
    • Android Binder Bridge: Support for abb and abb_exec.
    • ADB Daemon Control: Restarting adbd (root:, unroot:) and switching transports (usb:, tcpip:).
    • Miscellaneous: Managing the ADB server, remounting partitions, and mDNS discovery.
  8. Use Android Binder Bridge (ABB) to communicate with device services

    master

    The Android Binder Bridge (ABB) allows you to communicate directly with services on an Android device (e.g., the package service for package management). You can list all available services by including the -l flag in your request.

    To use the full ABB feature set (including stdout, stderr, and exit codes), your environment must support Feature.ABB.

    val result = adb.execute(
        request = AbbRequest(listOf("-l")),
        serial = "emulator-5554"
    )
    
    println(result.stdout)
  9. List files using the ls wrapper

    master

    You can traverse directories by executing a ListFilesRequest via the adb.execute method. This returns a list of AndroidFile objects representing the contents of the specified directory.

    val files: List<AndroidFile> = adb.execute(
        request = ListFilesRequest(
            directory = "/sdcard/"
        ),
        serial = "emulator-5554"
    )
  10. Implement a test runner for Adam Android JUnit 4 rules

    master

    To support Adam's JUnit 4 rules, a test runner must inject connection details (host, port, serial, etc.) into the tests via Instrumentation arguments. This is necessary because the test runner is the entity that establishes the connection to the ADB server, gRPC, or the emulator console.

    Add the following dependency to your test runner project:

    // Use the actual version from Maven Central
    implementation "com.malinskiy.adam:android-testrunner-contract:X.X.X"

    Use the constants provided in TestRunnerContract to define the argument names for the instrumentation command.

    $ am instrument -w -r --no-window-animation \
      -e class com.example.AdbActivityTest#testUnsafeAccess \
      -e debug false \
      -e com.malinskiy.adam.android.ADB_PORT 5037 \
      -e com.malinskiy.adam.android.ADB_HOST 10.0.2.2 \
      -e com.malinskiy.adam.android.ADB_SERIAL emulator-5554 \
      -e com.malinskiy.adam.android.GRPC_PORT 8554 \
      -e com.malinskiy.adam.android.GRPC_HOST 10.0.2.2 \
      com.example.test/androidx.test.runner.AndroidJUnitRunner
  11. Stream logcat output using ChanneledLogcatRequest

    master

    To continuously record logcat output (e.g., for writing to a file or real-time monitoring), use adb.execute with a ChanneledLogcatRequest. This returns a channel that you can consume within a coroutine scope.

    Important Note on Parsing: Logcat chunks received from the channel might not be newline (\n) terminated. If you need to process logs line-by-line, you must accumulate the chunks in a buffer first to ensure you have complete lines.

    ChanneledLogcatRequest supports similar configuration to the sync request, including since, modes, buffers, pid, lastReboot, and filters.

    launch {
        val channel = adb.execute(
            request = ChanneledLogcatRequest(),
            scope = this,
            serial = "emulator-5554"
        )
    
        val logcatChunk = channel.receive()
        // Process logcatChunk (e.g., write to file or buffer)
    
        // Dispose of channel to close the resources
        channel.cancel()
    }