How Universal Android Debloater Executes and Parses Android Package Manager Commands

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 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.

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 between lines 31-35, converting a generic shell command into a Package Manager-specific command builder.

// 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 (lines 40-74) handles the specific argument construction for listing system packages and filtering by user ID.

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 (lines 66-71) strips this prefix, validates the resulting package name via PackageId::new, and returns a Vec<String> containing clean package identifiers.

// 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 (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.

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.

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(...).

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 (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 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.

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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →