How the Configuration System Handles Device-Specific Settings in Universal Android Debloater

The Universal Android Debloater Next Generation uses a TOML-based configuration system that stores global settings in GeneralSettings and per-device preferences in a Vec<DeviceSettings>, keyed by ADB serial identifiers, with all persistence logic centralized in crates/uad-core/src/config.rs.

The Universal Android Debloater Next Generation (UAD-ng) manages both global application preferences and individual device configurations through a unified configuration system written in Rust. This architecture allows users to maintain distinct device-specific settings—such as disable mode or multi-user preferences—for each connected Android device while keeping universal options like themes and backup folders consistent across sessions.

Core Configuration Architecture

The configuration system is defined in crates/uad-core/src/config.rs and revolves around three primary structures that separate global concerns from device-specific settings.

The Config Struct

The Config struct serves as the top-level container that binds general application settings to a collection of device-specific settings. It is defined as:

struct Config {
    general: GeneralSettings,
    devices: Vec<DeviceSettings>,
}

This structure maps directly to the config.toml file stored in the user's configuration directory. The file path is determined at runtime by the static CONFIG_FILE constant, which uses LazyLock to resolve to <CONFIG_DIR>/config.toml according to the source code.

GeneralSettings vs DeviceSettings

The system cleanly separates universal preferences from per-device configurations:

  • GeneralSettings — Contains global options including theme: String, expert_mode: bool, and backup_folder: PathBuf. These apply to the entire application regardless of which device is connected.

  • DeviceSettings — Contains options specific to a single Android device, identified by its ADB serial number in the device_id: String field. Key fields include disable_mode: bool and multi_user_mode: bool, allowing different debloating behaviors for different phones or tablets.

How Device-Specific Settings Are Stored and Retrieved

Device-specific settings are stored as elements within the devices vector in the Config struct. The device_id field acts as the unique primary key for lookup operations.

When the application starts, Config::load_configuration_file() reads the TOML file from disk or creates a fresh configuration with defaults if the file is missing. To retrieve settings for a specific device, the code iterates through the vector matching against the ADB serial:

use uad_core::config::{Config, DeviceSettings};

let cfg = Config::load_configuration_file();
let target_id = "emulator-5554".to_string();

let device_cfg: Option<&DeviceSettings> = cfg.devices
    .iter()
    .find(|d| d.device_id == target_id);

if let Some(dev) = device_cfg {
    println!("Disable mode: {}", dev.disable_mode);
}

Persisting Changes with save_device_settings

Updates to device-specific settings are centralized through the Config::save_device_settings() method. This routine handles both insertion of new device entries and updates to existing ones, ensuring the TOML file on disk remains synchronized with runtime state.

The method performs the following operations:

  1. Searches the devices vector for an existing entry matching the incoming device_id.
  2. Overwrites the existing entry if found, or pushes the new DeviceSettings onto the vector.
  3. Updates the general field with the supplied GeneralSettings.
  4. Serializes the entire Config struct to TOML and writes it to CONFIG_FILE.
// Simplified excerpt from crates/uad-core/src/config.rs
if let Some(device) = self.devices.iter_mut()
                                   .find(|x| x.device_id == device_settings.device_id) {
    *device = device_settings;
} else {
    self.devices.push(device_settings);
}
self.general = general;
let toml = toml::to_string(&self).unwrap();
fs::write(&*CONFIG_FILE, toml).expect("Could not write config file to disk!");

Integration with the GUI and CLI

The configuration system is consumed by both the graphical and command-line interfaces, ensuring consistent behavior across interaction modes.

In the GUI layer (crates/uad-gui/src/views/settings.rs), the settings view loads the configuration, locates the entry matching the currently selected phone's adb_id, and populates the UI widgets. When users toggle options like "Disable mode," the view constructs a new DeviceSettings instance and invokes save_device_settings() to persist the changes immediately.

For CLI operations, crates/uad-cli/src/device.rs resolves target device IDs and retrieves their specific configurations, while crates/uad-cli/src/commands.rs implements batch operations that update settings programmatically.

Handling Backup Data and Defaults

The DeviceSettings struct includes a backup field of type BackupSettings, but this data is intentionally transient. Marked with #[serde(skip)], the field is excluded from serialization and exists only in memory during runtime. This design keeps the TOML configuration file focused on user preferences while backup metadata is handled separately.

If load_configuration_file() encounters a corrupted or unreadable configuration file, it falls back to Config::default(). This default implementation produces an empty devices vector and initializes GeneralSettings with DEFAULT_THEME and a backup folder located in the system's cache directory.

Summary

  • The configuration system uses a TOML file (config.toml) stored in the user's configuration directory, with paths managed by the CONFIG_FILE constant in crates/uad-core/src/config.rs.
  • Global settings are handled by GeneralSettings (theme, expert mode, backup folder), while device-specific settings use DeviceSettings keyed by ADB serial numbers.
  • The devices vector within the Config struct stores all per-device preferences, with save_device_settings() managing upsert operations and disk persistence.
  • Backup-related data in DeviceSettings is marked with #[serde(skip)] and remains in memory only, never written to the configuration file.
  • Both the GUI (crates/uad-gui/src/views/settings.rs) and CLI (crates/uad-cli/src/device.rs) interact with the same core configuration API, ensuring consistent device-specific behavior across interfaces.

Frequently Asked Questions

Where is the configuration file stored on my system?

The configuration file is located at <CONFIG_DIR>/config.toml, where <CONFIG_DIR> resolves to the user's standard configuration directory (e.g., ~/.config/universal-android-debloater/ on Linux). The exact path is determined at runtime by the CONFIG_FILE static variable in crates/uad-core/src/config.rs.

How does UAD-ng distinguish settings between different Android devices?

Each device is identified by its unique ADB serial number, stored in the device_id field of the DeviceSettings struct. When you connect a device, the application looks up its serial in the devices vector of the configuration; if found, it loads the associated settings, otherwise it creates a new entry with defaults.

What happens to my settings if the configuration file becomes corrupted?

If the TOML file cannot be parsed during load_configuration_file(), the system automatically falls back to Config::default(). This generates a fresh configuration with an empty device list and default global settings, effectively resetting the application to factory defaults without crashing.

Why don't backup settings persist in the configuration file?

The backup field within DeviceSettings is annotated with #[serde(skip)], which instructs the TOML serializer to ignore it. This intentional design choice keeps backup metadata in memory only, preventing sensitive or temporary path data from accumulating in the persistent configuration file while maintaining per-device backup state during the session.

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 →