How Universal Android Debloater Next Generation Handles App Data: A Deep Dive into the Core Logic

Universal Android Debloater Next Generation automatically clears app data by injecting the pm clear command as the final step in every state transition sequence, ensuring no residual data remains after disabling or uninstalling packages.

Understanding how Universal Android Debloater Next Generation (UADNG) manages app data is crucial for users who want complete removal of bloatware without leaving traces behind. The application treats data clearing as a first-class concern, embedding it directly into the core state transition logic across all supported Android versions. This article examines the Rust-based implementation that ensures consistent data wiping across devices and user profiles.

The Architecture of App Data Management in UADNG

The UADNG architecture isolates app data handling within the uad-core crate, specifically coordinating between the ADB wrapper layer and the synchronization logic. When you request a package state change through either the GUI or CLI, the system constructs a deterministic sequence of shell commands that always terminates with a data-clear operation where appropriate.

State Transition Logic and Command Composition

All package state modifications flow through the apply_pkg_state_commands function located in crates/uad-core/src/sync.rs. This function serves as the central dispatcher that determines which Android Package Manager (pm) commands to execute based on:

  • The target PackageState (Enabled, Disabled, Uninstalled)
  • The device's SDK version (Android 5.x, 6.0+, or pre-5.0)
  • The presence of multiple user profiles

The function returns a Vec<String> containing the ordered command sequence. According to the source code comment at line 71 of sync.rs: "ALWAYS PUT THE COMMAND THAT CHANGES THE PACKAGE STATE FIRST!" This ensures that pm clear executes only after the package has successfully transitioned to its new state.

The PM_CLEAR_PACK Constant

The actual data-clearing mechanism relies on the constant PM_CLEAR_PACK, defined in crates/uad-core/src/adb.rs. This constant holds the string value "pm clear", serving as the atomic building block for all data-wiping operations:

// In crates/uad-core/src/adb.rs
pub const PM_CLEAR_PACK: &str = "pm clear";

By abstracting the command into a constant, UADNG ensures consistent usage across the entire codebase while making future modifications trivial.

How Commands Are Built and Executed

Command Sequencing in sync.rs

The request_builder function (lines 8-13 of sync.rs) constructs the final command strings by concatenating:

  1. The base command (pm disable-user, pm hide, pm block, etc.)
  2. The optional user flag (--user <id>)
  3. The target package name
  4. The PM_CLEAR_PACK command (appended last for relevant transitions)

For a disable operation on Android 6.0+ (SDK ≥ 23), the generated command vector looks like:

let commands = apply_pkg_state_commands(
    &core_pkg,                // CorePackage describing the app
    PackageState::Disabled,   // Desired state
    selected_user,            // User struct
    &phone,                   // Phone information (SDK version, etc.)
);

This produces the sequence:


pm disable-user --user <uid> <package>
am force-stop --user <uid> <package>
pm clear --user <uid> <package>

Multi-User Support with User Flags

On multi-user devices, UADNG appends the --user <id> flag to ensure data clearing affects only the intended profile. The user_flag variable captures this identifier early in the process, and request_builder injects it into every command including the final pm clear operation. This prevents cross-contamination between user profiles while ensuring complete data removal within the selected context.

Execution Flow via ACommand

The execute_first_command function (lines 74-84 of sync.rs) handles command execution using the ACommand wrapper around ADB shell sessions. Because pm clear appears last in the command vector for transitions that require data wiping, it executes only after the state-changing command succeeds:

let result = execute_first_command(&commands, &phone);
match result {
    Ok(_) => println!("Package state changed successfully."),
    Err(e) => eprintln!("Failed: {e}"),
}

The system uses ACommand::new().shell(&phone.adb_id).raw(&command) to dispatch instructions to the device, ensuring raw shell access for privileged operations.

Android Version-Specific Handling

UADNG adapts its data-clearing strategy based on the Android version detected on the target device, as different API levels support different package manager commands.

Disabling on Android 6.0+

For devices running Marshmallow or newer, disabling a package triggers three distinct commands:

  1. pm disable-user – Changes the package state to disabled
  2. am force-stop – Terminates running processes
  3. pm clear – Wipes the application data directory

This sequence ensures that when you disable a system app through UADNG, no cached data or user preferences persist on the device.

Uninstalling on Legacy Android

Older Android versions require alternative approaches that still preserve data integrity:

  • Android 5.x: Uses pm hide followed by pm clear
  • Pre-Android 5.0: Uses pm block followed by pm clear

In both legacy cases, the data-clear command remains the terminal operation, ensuring that hidden or blocked packages do not consume storage space with residual data files.

Error Handling and Fallback Strategies

When a state transition fails, UADNG invokes attempt_fallback to retry with alternative methods (such as attempting a disable when uninstall fails). Critically, these fallback paths also include PM_CLEAR_PACK where appropriate, guaranteeing that stray data does not persist after a failed operation.

For manual data clearing without a state transition, you can invoke the builder directly:

let clear_cmd = format!("{} {}", PM_CLEAR_PACK, package_name);
ACommand::new()
    .shell(&phone.adb_id)
    .raw(&clear_cmd)
    .expect("Failed to clear app data");

This pattern proves useful in the CLI implementation located in crates/uad-cli/src/commands.rs, where specific data management operations may be requested independent of package state changes.

Summary

  • Universal Android Debloater Next Generation treats app data clearing as an integral part of package state transitions, not an optional afterthought.
  • The PM_CLEAR_PACK constant in crates/uad-core/src/adb.rs provides the atomic "pm clear" command used across all operations.
  • The apply_pkg_state_commands function in crates/uad-core/src/sync.rs automatically appends data clearing as the final command in every sequence that could leave stale data.
  • Commands execute in strict order: state change first, process termination second, data clearing last, ensuring the package is already in the desired state before its data is wiped.
  • Multi-user devices receive proper --user <id> flag injection to prevent cross-profile data contamination.
  • Fallback mechanisms ensure data clearing occurs even when primary state transitions fail, preventing residual data accumulation.

Frequently Asked Questions

What happens to app data when I disable a package using UADNG?

When you disable a package, UADNG executes a three-step sequence: first pm disable-user to change the state, then am force-stop to kill the process, and finally pm clear to wipe the data directory. This ensures the app cannot run and leaves no data behind, effectively reclaiming storage space while keeping the package technically installed for system integrity.

Does UADNG support clearing data for specific user profiles only?

Yes, UADNG fully supports multi-user Android devices. The request_builder function automatically appends the --user <id> flag to every command including pm clear, ensuring that data clearing affects only the selected user profile. This prevents accidental data loss in other profiles while allowing precise debloating across shared devices.

Why does UADNG clear data after changing the package state rather than before?

The sequence follows the strict rule "ALWAYS PUT THE COMMAND THAT CHANGES THE PACKAGE STATE FIRST!" as documented in crates/uad-core/src/sync.rs. This ordering ensures that the package is already in its desired state (disabled, hidden, or blocked) before the data wipe occurs. If the state change were to fail, the data remains intact, allowing for retry attempts without requiring a complete reinstallation or data restoration.

Can I clear app data without disabling or uninstalling the package?

While the primary UI flow couples data clearing with state transitions, the underlying ACommand API allows direct data clearing. The CLI implementation in crates/uad-cli/src/commands.rs demonstrates how to construct manual clear commands using the PM_CLEAR_PACK constant. However, for most use cases, the automatic clearing during disable/uninstall operations provides the cleanest device state.

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 →