# How the UAD-ng ADB Module Architecture Works: Type-Safe Builder Pattern Explained

> Uncover the UAD-ng ADB module architecture and its type-safe builder pattern. Ensure compile-time validation for secure and efficient Android debloating.

- Repository: [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
- Tags: architecture
- Published: 2026-06-18

---

**The UAD-ng ADB module implements a thin, type-state wrapper around the Android Debug Bridge CLI, ensuring compile-time validation of command sequences through a consuming builder pattern.**

The Universal Android Debloater Next Generation (UAD-ng) interacts with Android devices through a carefully architected abstraction layer. According to the [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation) source code, the **ADB module architecture** follows a low-overhead design that maps each Rust method directly to a single `adb` CLI command without hidden magic or custom sub-commands.

## Core Design Philosophy

The architecture prioritizes **thin wrappers** over heavy abstraction. Every method in the module translates to exactly one CLI argument, maintaining transparency and debuggability. The design employs **strong typing** through types like `PackageId`, `PmListPacksFlag`, and `UserInfo` to encode invariants that would otherwise be stringly-typed.

This approach delivers three key benefits:

- **Compile-time safety** via the type-state pattern prevents illegal command compositions
- **Zero-overhead abstraction** with no hidden state or fallback to raw `Command` usage
- **Direct traceability** where every Rust method maps 1-to-1 to an `adb` executable invocation

## The ACommand Entry Point

The core struct resides in [`crates/uad-core/src/adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/adb.rs) at lines 82-87:

```rust
pub struct ACommand(std::process::Command);

```

The `new()` constructor spawns a `std::process::Command` targeting the **`adb`** executable. This struct serves as the foundation for all higher-level builders, guaranteeing that every method chain ultimately resolves to a valid `adb` invocation. Unlike generic process wrappers, `ACommand` enforces device specificity and context validation at the type level.

## Builder Hierarchy and Type-State Pattern

The module implements a consuming builder hierarchy where each stage **consumes** the previous struct, preventing illegal state transitions at compile time.

### ACommand::shell() and ShellCommand

Defined at lines 90-101 in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs), the `shell()` method initiates an `adb shell …` session for a specific device:

```rust
pub fn shell(self, serial: impl AsRef<str>) -> ShellCommand

```

This returns a `ShellCommand` struct (lines 124-162), which wraps the shell context and exposes higher-level sub-commands including `pm()`, `getprop()`, `reboot()`, and a generic `raw` executor. Once you transition to `ShellCommand`, you cannot accidentally invoke non-shell ADB operations—the compiler enforces this boundary.

### PmCommand for Package Manager Operations

For Android Package Manager operations, the `ShellCommand.pm()` method returns a `PmCommand` instance (lines 237-340). This specialized builder handles `pm` sub-commands such as listing packages, clearing data, and managing components:

```rust
pub fn pm(self) -> PmCommand

```

The type-state pattern ensures you cannot call `pm()` after issuing a non-shell command, eliminating entire classes of runtime errors through Rust's ownership system.

## Command Execution Flow

All builders eventually delegate to the private `run()` method located at lines 184-215 in [`crates/uad-core/src/adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/adb.rs):

```rust
fn run(self) -> Result<String, String> {
    let mut cmd = self.0;
    info!("Ran command: adb {}", ...);
    match cmd.output() {
        Err(e) => Err("Cannot run ADB, likely not found".into()),
        Ok(o) => {
            let stdout = to_trimmed_utf8(&o.stdout);
            if o.status.success() { Ok(stdout) } else { Err(stderr_or_stdout) }
        }
    }
}

```

This method handles UTF-8 conversion through the `to_trimmed_utf8` helper, error bubbling, and logs the exact command line for debugging. The execution layer remains self-contained, with utility helpers like `PackageId` validation living within the same module to validate Android package identifiers against official manifest rules.

## Integration Across Crates

Other crates consume the ADB API via re-exports from `uad_core`, maintaining a minimal public surface area.

**CLI Integration (`uad-cli`)**: The CLI crate drives the ADB layer for commands like `list-packages`, `reboot`, and `devices`. In [`crates/uad-cli/src/commands.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-cli/src/commands.rs) (lines 134-174), you'll find invocations such as:

```rust
ACommand::new().shell(serial).pm().list_packages_sys(...)

```

**GUI Integration (`uad-gui`)**: The graphical interface displays ADB version and device info using the same abstraction. The [`about.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/about.rs) file (line 108) retrieves version information through the typed API.

**Sync Logic ([`sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/sync.rs))**: Background synchronization employs `ACommand` to clear packages and fetch user lists, demonstrating the module's thread-safe design.

All callers import the type from the core crate:

```rust
use uad_core::adb::ACommand;

```

## Practical Code Examples

### List Attached Devices

```rust
use uad_core::adb::ACommand;

fn list_devices() -> Result<Vec<(String, String)>, String> {
    ACommand::new().devices()
}

```

### Retrieve ADB Version

```rust
let version = ACommand::new().version()?; // returns the raw version string
println!("ADB version: {version}");

```

### Query Device Properties

```rust
let serial = "emulator-5554";
let prop = ACommand::new()
    .shell(serial)
    .getprop("ro.build.version.release")?;
println!("Android version: {prop}");

```

### List System Packages for Specific Users

```rust
let pkgs = ACommand::new()
    .shell("0123456789ABCDEF")
    .pm()
    .list_packages_sys(Some(PmListPacksFlag::OnlyEnabled), Some(0))?;
for p in pkgs {
    println!("Package: {p}");
}

```

### Reboot a Device

```rust
ACommand::new()
    .shell("my-device-serial")
    .reboot()?; // triggers `adb -s my-device-serial reboot`

```

## Summary

- The **UAD-ng ADB module architecture** resides in [`crates/uad-core/src/adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/adb.rs) and implements a type-safe wrapper around the `adb` CLI.
- **ACommand** serves as the entry point, with consuming builders (`ShellCommand`, `PmCommand`) enforcing valid command sequences at compile time.
- The **type-state pattern** prevents illegal transitions, such as calling package manager methods outside shell contexts.
- Command execution flows through a centralized `run()` method that handles UTF-8 conversion, error propagation, and logging.
- The module integrates across CLI, GUI, and sync crates through re-exports in `uad_core`, maintaining a minimal, well-documented public API.

## Frequently Asked Questions

### What is the type-state pattern in UAD-ng's ADB module?

The type-state pattern uses Rust's ownership system to enforce valid command sequences at compile time. Each builder method **consumes** the previous struct (e.g., `ACommand` → `ShellCommand` → `PmCommand`), making it impossible to call methods in invalid orders. For example, you cannot invoke `pm()` without first calling `shell()` because the compiler tracks the state transitions through distinct types.

### How does UAD-ng handle ADB command execution errors?

The private `run()` method in [`crates/uad-core/src/adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/adb.rs) (lines 184-215) handles all execution errors. It captures `std::process::Command` output, converts raw bytes to trimmed UTF-8 using `to_trimmed_utf8`, and returns `Result<String, String>`. If the `adb` executable is missing, it returns "Cannot run ADB, likely not found". Successful executions return stdout; failures return stderr or stdout depending on availability.

### Where is the core ADB implementation located in the repository?

The core implementation lives in **[`crates/uad-core/src/adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/adb.rs)**. This file contains the `ACommand` struct, `ShellCommand` and `PmCommand` builders, the `run()` execution method, and utility helpers like `to_trimmed_utf8` and `PackageId`. Integration points exist in [`crates/uad-cli/src/commands.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-cli/src/commands.rs) for CLI operations and [`crates/uad-gui/src/views/about.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/about.rs) for GUI functionality.

### How does the ADB module ensure type safety for Android packages?

The module uses the **`PackageId`** type to validate Android package identifiers according to official manifest rules, preventing malformed package names from reaching the device. Additionally, flags like `PmListPacksFlag` encode valid package manager options as enumerated types rather than raw strings, eliminating typo-based errors and invalid flag combinations at compile time.