How Universal Android Debloater Next Generation Supports Multi-User Android Devices and User Enumeration

Universal Android Debloater Next Generation (UAD-NG) treats every connected device as a Phone struct containing a Vec<User> populated via ADB queries, enabling granular per-user package management while automatically protecting work-profile users and supporting bulk operations across all device profiles.

Managing Android devices with multiple user profiles requires careful handling of user IDs, protected system accounts, and cross-user package dependencies. The Universal Android Debloater Next Generation (UAD-NG) implements a robust multi-user architecture in its Rust codebase, modeling each device as a structured object that enumerates all Android users before executing any debloating commands. This design allows administrators to target specific users or apply changes globally while preventing accidental modification of protected work profiles.

Core Data Structures for Multi-User Devices

UAD-NG defines its multi-user capabilities through two primary structures located in crates/uad-core/src/sync.rs.

The Phone struct represents the physical device and contains a user_list: Vec<User> field that holds all enumerated accounts. When a device connects, the application populates this vector by querying the Android Package Manager through ADB.

The User struct tracks individual Android users with three critical fields:

  • id: The actual Android user ID (integer) used in ADB commands
  • index: The position within the user_list vector for UI reference
  • protected: A boolean flag indicating system or work-profile users that cannot be modified
use uad_core::sync::{Phone, User};

fn printable_users(phone: &Phone) -> Vec<String> {
    phone.user_list
        .iter()
        .filter(|u| !u.protected)          // hide work‑profile or system users
        .map(|u| format!("user {} (index {})", u.id, u.index))
        .collect()
}

Enumerating Android Users via ADB

User enumeration occurs immediately upon device detection. The system queries ADB using package manager commands to discover all valid user profiles on the device.

According to the source code in crates/uad-core/src/adb.rs, the enumeration process runs pm list users or validates users via pm list packages --user <id> to test each potential ID. Successful queries populate the Phone.user_list with User structs, while failed queries indicate protected or invalid user slots.

This enumeration enables the GUI in crates/uad-gui/src/views/list.rs to display a radio-button pick-list populated from selected_device.user_list.clone(), allowing operators to select specific targets before executing commands.

Detecting Multi-User Capability

Not all Android devices support multiple users. UAD-NG implements version-gated detection through the supports_multi_user(dev: &Phone) -> bool function in crates/uad-core/src/sync.rs.

The function checks if dev.android_sdk >= 21, corresponding to Android Lollipop (API level 21). When this returns false, the UI disables the multi-user toggle in crates/uad-gui/src/views/settings.rs, preventing operators from attempting unsupported operations on older devices.

use uad_core::sync::{Phone, supports_multi_user};

let phone = Phone::default();               // Filled elsewhere after ADB detection
if supports_multi_user(&phone) {
    println!("Device may have multiple users (SDK ≥ 21)");
}

Building User-Specific ADB Commands

When executing package operations, UAD-NG constructs ADB commands that include the --user flag to target specific profiles. The user_flag(user: Option<User>) -> String function in crates/uad-core/src/sync.rs returns --user <id> when a user is specified, or an empty string for device-wide operations.

The apply_pkg_state_commands function receives the selected_user: User and builds per-user command sequences. If Multi-User Mode is enabled via the settings checkbox (self.device.multi_user_mode), the system iterates through all non-protected users in user_list and dispatches the same command to each, effectively debloating all profiles simultaneously.

use uad_core::sync::{apply_pkg_state_commands, request_builder, user_flag, User};

let user = User { id: 10, index: 2, protected: false };
let commands = apply_pkg_state_commands(&core_pkg, PackageState::Uninstalled, user, &phone);
let adb_cmds = request_builder(&commands, &core_pkg.name, Some(user));
// adb_cmds now contain strings like "pm uninstall --user 10 com.example.app"

Protecting System and Work-Profile Users

UAD-NG safeguards critical user profiles through the is_protected_user(user_id, device_serial) function in crates/uad-core/src/sync.rs. This validation attempts to run pm list packages -s --user <id>; if the call fails, the user is marked as protected (typically indicating a work profile or restricted system user).

Protected users are filtered from the selection UI in crates/uad-gui/src/views/list.rs and are automatically excluded from bulk operations even when Multi-User Mode is active. This prevents accidental modification of corporate work profiles or restricted accounts that could violate MDM policies or brick the device.

Detecting Cross-User Behavior

Android packages often exhibit cross-user dependencies where removing an app from one profile affects its availability in others. UAD-NG monitors these interactions through the detect_cross_user_behavior function in crates/uad-core/src/sync.rs.

After package operations complete, the system checks whether the package appears or disappears on other user profiles. When cross-user restoration or uninstallation is detected, the application warns the operator, providing visibility into system-wide package changes that might otherwise appear unexpected.

Summary

  • Device Modeling: UAD-NG represents devices as Phone structs containing a vector of User structs, enabling structured multi-user management.
  • User Enumeration: The system queries ADB via pm list users and pm list packages --user commands in crates/uad-core/src/adb.rs to populate the user list.
  • Version Detection: Multi-user support is gated by supports_multi_user, which verifies android_sdk >= 21 before enabling related UI features.
  • Command Construction: The user_flag and apply_pkg_state_commands functions inject --user <id> flags into ADB commands for precise targeting.
  • Protection Mechanisms: Work-profile and system users are identified via is_protected_user and automatically excluded from modification operations.
  • Cross-User Awareness: The detect_cross_user_behavior function monitors package state changes across profiles to prevent unintended side effects.

Frequently Asked Questions

How does UAD-NG handle work profile users?

Work profile users are detected as protected accounts through the is_protected_user function, which attempts to list packages for the specific user ID and marks the user as protected if the ADB call fails. These users are hidden from the selection UI and excluded from bulk operations to prevent corporate policy violations or device instability.

What Android version is required for multi-user support in UAD-NG?

UAD-NG requires Android Lollipop (API level 21) or higher for multi-user capabilities. The supports_multi_user function checks the device's android_sdk field, and the GUI disables the "Affect all users" checkbox on devices running older versions.

Can I apply debloating actions to all users at once?

Yes. When you enable the "Affect all the users of the device" checkbox in crates/uad-gui/src/views/settings.rs, UAD-NG iterates through all non-protected users in Phone.user_list and dispatches the same ADB command to each user ID, appending the --user flag to each invocation.

How does the application detect if a package operation affected other users?

After executing commands, UAD-NG runs detect_cross_user_behavior to check if the target package's state changed on other user profiles. If the package disappeared from or appeared on additional users, the system reports cross-user uninstallation or restoration, alerting you to system-wide changes.

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 →