# How the ADB Command Wrapper Architecture Uses the Type-State Pattern in UADNG

> Discover how UADNG leverages the type-state pattern in its ADB command wrapper architecture. Learn to build safe and valid ADB command sequences at compile time.

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

---

**The Universal Android Debloater Next Generation (UADNG) implements a zero-cost, compile-time safe ADB command builder 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) using the type-state pattern, where consuming methods like `shell()` and `pm()` transform builders through distinct types to enforce valid Android Debug Bridge command sequences.**

The Universal Android Debloater Next Generation project provides a Rust-based tool for managing Android device packages through ADB. Instead of constructing raw command strings that risk runtime errors, the **ADB command wrapper architecture** leverages Rust's ownership system to enforce correct command ordering at compile time. This design lives primarily in the `uad-core` crate and provides a type-safe API used by both the GUI and CLI components.

## Overview of the Type-State Pattern in UADNG

The type-state pattern in UADNG models ADB commands as a linear progression of builder objects. Each struct represents a valid state in the command construction process, and methods consume the current state to produce the next.

This approach prevents illegal sequences—such as calling package manager methods before establishing a shell context—by making them unrepresentable in the type system. The implementation adds virtually no runtime overhead, as the builders are thin wrappers around `std::process::Command`.

## The Builder Hierarchy: From ACommand to PmCommand

The architecture defines three primary builder types 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), each wrapping `std::process::Command` and adding context-specific methods.

### ACommand: The Entry Point

`ACommand` serves as the root builder, initialized via `ACommand::new()` (lines 86-88). It represents the base `adb` invocation and provides methods to branch into specific sub-commands:

- `shell(device_serial)` - Returns a `ShellCommand` after appending the `-s` flag (if provided) and `shell` argument (lines 94-101)
- `devices()` - Lists connected devices
- `version()` - Retrieves ADB version information (lines 130-138)

### ShellCommand: Device Shell Context

Created by calling `shell()` on an `ACommand`, this builder represents an `adb shell` invocation. It wraps the parent command and enables device-specific operations:

- `pm()` - Consumes the shell context and returns a `PmCommand` (lines 32-35)
- `getprop()` - Accesses system properties
- `reboot()` - Triggers device restart (lines 48-52)
- `raw()` - Executes arbitrary shell commands

### PmCommand: Package Manager Operations

The final builder in the standard chain, `PmCommand` methods append arguments to the `pm` (package manager) sub-command:

- `list_packages_sys(filter, user_id)` - Lists installed packages with optional filtering (lines 48-74)
- `list_users()` - Enumerates device users

Both methods eventually call the shared `run()` implementation defined on `ACommand` (lines 120-128) to execute the constructed process.

## Compile-Time Safety Through Consumption

The **type-state pattern** enforcement relies on methods taking `self` by value, consuming the previous builder. This ownership transfer prevents reuse of intermediate states and ensures linear command construction.

Consider the method signatures:

```rust
pub fn shell<S: AsRef<str>>(self, device_serial: S) -> ShellCommand;
pub fn pm(self) -> PmCommand;

```

Since `shell()` consumes `ACommand` and `pm()` consumes `ShellCommand`, the compiler rejects invalid sequences like `ACommand::new().pm()` or reusing a shell context after converting it to a package manager command.

## Practical Usage Examples

### Retrieving ADB Version

The simplest usage queries the ADB version without device interaction:

```rust
let version = adb::ACommand::new()
    .version()
    .expect("Failed to get adb version");
println!("adb version: {version}");

```

*Source:* [`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 130-138)

### Listing System Packages

To list enabled packages for a specific user, chain through the full hierarchy:

```rust
let packages = adb::ACommand::new()
    .shell("")                // empty serial uses default device
    .pm()
    .list_packages_sys(Some(adb::PmListPacksFlag::OnlyEnabled), Some(0))
    .expect("Failed to list packages");
for pkg in packages {
    println!("{pkg}");
}

```

*Source:* `ShellCommand::pm()` (lines 32-35) and `PmCommand::list_packages_sys` (lines 48-74)

### Rebooting a Device

For device control operations available at the shell level:

```rust
adb::ACommand::new()
    .shell("0123456789ABCDEF")  // specific device serial
    .reboot()
    .expect("Reboot failed");

```

*Source:* `ShellCommand::reboot` (lines 48-52)

## Integration in GUI and CLI

The **ADB command wrapper architecture** serves both the graphical and command-line interfaces uniformly.

In the GUI ([`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), line 108), the about view retrieves version information:

```rust
let adb_version_text = text(match adb::ACommand::new().version() { … });

```

In the CLI ([`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), line 134), REPL commands fetch package data using the same builder chain:

```rust
let system_packages = ACommand::new()
    .shell(serial)
    .pm()
    .list_packages_sys(None, None)?;

```

This shared abstraction ensures consistency across the application while maintaining type safety.

## Summary

- The **type-state pattern** in UADNG enforces valid ADB command sequences through Rust's type system, preventing runtime errors from malformed commands.
- The builder hierarchy—**`ACommand`** → **`ShellCommand`** → **`PmCommand`**—models the natural progression of `adb shell pm` invocations.
- Each transition consumes the previous builder, making illegal states unrepresentable and providing zero-cost abstractions over `std::process::Command`.
- Both GUI and CLI crates in `crates/uad-gui` and `crates/uad-cli` leverage the core implementation 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).

## Frequently Asked Questions

### What is the type-state pattern in Rust?

The type-state pattern encodes state machine transitions into the type system itself. In UADNG, each valid stage of ADB command construction becomes a distinct struct (like `ShellCommand` or `PmCommand`), and methods only exist on types where those operations are valid. This moves error detection from runtime to compile time.

### Why does UADNG use consuming methods (taking `self` by value) instead of `&mut self`?

Consuming methods ensure that once you transition from `ACommand` to `ShellCommand`, you cannot accidentally reuse the original `ACommand` or call methods out of order. This prevents bugs where a developer might try to execute a partially constructed command or modify a shell context after converting it to a package manager operation.

### Can I extend the ADB wrapper to support new sub-commands?

Yes. The architecture allows extension by defining new builder structs that consume appropriate predecessors. For example, to add `logcat` support, you could implement a `LogcatCommand` returned by `ShellCommand::logcat()`, following the same pattern of consuming `self` and appending arguments before returning the new type.

### Where is the actual ADB process execution handled?

The `run()` method implemented on `ACommand` (lines 120-128 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)) handles the final execution. All builders in the chain ultimately invoke this method through the wrapped `std::process::Command`, capturing output and handling errors consistently across the application.