# How Universal Android Debloater Executes and Parses Android Package Manager Commands

> Discover how Universal Android Debloater Next Generation builds and executes Android Package Manager commands using a Rust architecture. Learn about AdbCommand and PmCommand builders and output parsing.

- 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

---

**Universal Android Debloater Next Generation (UAD-ng) executes Android Package Manager commands through a three-layer Rust architecture that constructs ADB shell commands via `AdbCommand` and `PmCommand` builders, executes them through `ACommand::run`, and parses raw text output to extract package names and user IDs.**

The Universal Android Debloater Next Generation (UAD-ng) communicates with Android devices using the Android Debug Bridge (ADB) to execute Package Manager (`pm`) commands. Understanding how these Android Package Manager commands are constructed, transmitted, and parsed reveals the robust Rust-based architecture that isolates command generation from execution logic.

## Constructing Android Package Manager Commands

UAD-ng uses a builder pattern to assemble `pm` commands, starting from a generic ADB shell command and progressively specializing it for package management operations.

### Initializing the ADB Shell Connection

The process begins 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) where `AdbCommand::new().shell(serial)` creates an `ACommand` instance that wraps the low-level `adb shell` invocation. This establishes the base transport layer for sending commands to a specific device identified by its serial number.

```rust
use uad_core::adb::AdbCommand;

// Create base shell command for a specific device
let shell_cmd = AdbCommand::new().shell("device_serial");

```

### Adding the pm Prefix

The `ShellCommand::pm()` method mutates the underlying `ACommand` to prepend the literal string `"pm"` to the argument list. This transformation occurs in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs) between lines 31-35, converting a generic shell command into a Package Manager-specific command builder.

```rust
// Transforms into "pm ..." command base
let pm_cmd = shell_cmd.pm();

```

### Building Sub-commands

The returned `PmCommand` struct provides methods for each `pm` sub-command, such as `list_packages_sys` and `list_users`. These methods append the appropriate arguments (`list`, `packages`, `-s`, `-e`, `--user`, etc.) to the command vector. The implementation in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs) (lines 40-74) handles the specific argument construction for listing system packages and filtering by user ID.

```rust
use uad_core::adb::PmListPacksFlag;

// Build: pm list packages -s -e --user 0
let packages = AdbCommand::new()
    .shell("")
    .pm()
    .list_packages_sys(Some(PmListPacksFlag::OnlyEnabled), Some(0))
    .unwrap();

```

## Executing Commands on the Device

Once the command arguments are assembled, the builder calls `self.0.0.run()` (accessing the internal `ACommand` instance) to execute the assembled command on the device. This execution happens synchronously, returning the raw stdout and stderr streams from the ADB shell session.

The execution model separates command construction from I/O operations, allowing the same builder pattern to be used for both querying state (`pm list`) and modifying state (`pm disable-user`).

## Parsing pm Command Output

UAD-ng implements dedicated parsers for different `pm` sub-command outputs, converting raw text streams into structured Rust types.

### Parsing Package Lists

The output of `pm list packages -s` follows a newline-separated format where each line starts with `package:`. The parser in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs) (lines 66-71) strips this prefix, validates the resulting package name via `PackageId::new`, and returns a `Vec<String>` containing clean package identifiers.

```rust
// Raw output: "package:com.android.bluetooth\npackage:com.android.camera..."
// Parsed into: vec!["com.android.bluetooth", "com.android.camera", ...]

```

### Parsing User Lists

For `pm list users`, the output contains rows such as `UserInfo{0:owner:...}`. The parser implementation in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs) (lines 76-103) discards the header line, trims whitespace, strips the `UserInfo{` wrapper, splits on colons, and extracts the numeric user ID into a `UserInfo` struct.

```rust
use uad_core::adb::AdbCommand;

let users = AdbCommand::new()
    .shell("")
    .pm()
    .list_users()
    .unwrap();

// Extracts user IDs from "UserInfo{0:owner:...}" format
for user in users {
    println!("User ID: {}", user.get_id());
}

```

## High-Level Command Composition

While the `PmCommand` builder handles low-level queries, state-changing operations follow a different path through [`crates/uad-core/src/sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/sync.rs).

### State Change Logic

The `apply_pkg_state_commands` function (lines 62-88) defines the raw `pm` command strings (`"pm enable"`, `"pm disable-user"`, `"pm uninstall"`, etc.) based on the target `PackageState` and Android SDK version. This function determines which specific `pm` sub-command to use for enabling, disabling, or uninstalling packages.

### Request Building

The `request_builder` function (lines 8-14) appends a `--user <id>` flag if the device supports multiple users, then interpolates the target package name into the command string. The final vector of strings is sent to the device via `AdbCommand::shell(...).raw(...)`.

```rust
use uad_core::sync::{apply_pkg_state_commands, request_builder, PackageState};
use uad_core::adb::User;

// Generate commands to disable a package for user 0
let cmds = apply_pkg_state_commands(&pkg, PackageState::Disabled, User::new(0), &phone);
let adbs = request_builder(&cmds, &pkg.name, Some(User::new(0)));

// Results in: ["pm disable-user --user 0 com.example.app"]
for cmd in adbs {
    let _ = AdbCommand::new()
        .shell(serial)
        .raw(&cmd);
}

```

## Error Handling and UI Integration

The GUI layer occasionally needs to extract the offending package name from ADB error strings. In [`crates/uad-gui/src/views/list.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/list.rs) (lines 1178-1182), the code searches error text for the literal `"pm enable --user "` and slices out the remainder to identify which package caused a permission or execution failure.

This parsing of error messages allows the UI to provide specific feedback when Android Package Manager commands fail due to system restrictions or protected packages.

## Summary

- **Command construction** uses a builder pattern (`AdbCommand` → `ShellCommand` → `PmCommand`) to progressively assemble ADB shell commands with the `pm` prefix.
- **Execution** occurs through `ACommand::run()` after building argument lists like `["list","packages","-s","--user","0"]`.
- **Package list parsing** strips the `package:` prefix from `pm list packages` output and validates identifiers via `PackageId::new`.
- **User list parsing** extracts numeric IDs from `UserInfo{...}` structures returned by `pm list users`.
- **High-level composition** in [`sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/sync.rs) translates UI actions into raw pm command strings, handling multi-user support via the `--user` flag.
- **Error recovery** in the GUI layer parses command strings from ADB error output to identify failing packages.

## Frequently Asked Questions

### How does UAD-ng construct the pm command before sending it to ADB?

UAD-ng uses a builder pattern starting with `AdbCommand::new().shell(serial)` to create a base shell command, followed by `.pm()` to prepend the `"pm"` string, and finally specific methods like `list_packages_sys()` or `disable_user()` to append sub-command arguments. This architecture is implemented 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).

### What parsing logic handles the output of `pm list packages`?

The parser splits the newline-separated output and strips the `package:` prefix from each line (e.g., converting `package:com.android.bluetooth` to `com.android.bluetooth`). It then validates each entry through `PackageId::new` before returning a `Vec<String>` of clean package names.

### Where does the tool handle multi-user support in pm commands?

Multi-user support is handled in [`crates/uad-core/src/sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/sync.rs) by the `request_builder` function, which appends `--user <id>` to commands when the device supports multiple users. This applies to all state-changing operations like enable, disable, and uninstall.

### How does the GUI recover package names from ADB error messages?

When ADB returns an error, the GUI code in [`crates/uad-gui/src/views/list.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/list.rs) searches the error string for literals like `"pm enable --user "` and extracts the package name that follows, allowing the interface to display which specific package caused the failure.