How Device-Specific Settings Are Isolated in UAD-NG’s Configuration System

UAD-NG isolates device-specific settings by storing them in a Vec<DeviceSettings> within the global Config struct, using unique device_id fields to separate preferences per Android device while maintaining a single config.toml file.

UAD-NG (Universal Android Debloater Next Generation) manages user preferences for multiple Android devices through a centralized configuration system defined in the core crate. The architecture ensures that each connected device maintains independent settings without namespace collisions, storing all data in a single TOML file located in the application’s configuration directory.

Configuration Architecture and the Config Struct

All persistent user preferences reside in config.toml, which is defined and managed through the Config type in crates/uad-core/src/config.rs. This struct serves as the single source of truth for both the GUI and CLI front-ends, containing a general field for application-wide settings and a devices vector that houses per-device configurations.

The devices field holds a Vec<DeviceSettings>, where each entry represents the complete configuration state for one specific Android device. This vector-based structure provides the foundation for device-specific settings isolation within the shared file.

The DeviceSettings Struct and Device Identification

The DeviceSettings struct defined in crates/uad-core/src/config.rs contains the fields necessary to customize behavior for individual devices. Each instance includes a device_id field that acts as a unique identifier, ensuring the application can distinguish between different Android devices reliably.

When a user modifies settings for a specific device, the application creates a DeviceSettings instance populated with that device's device_id and the new preference values. This struct also contains a backup field of type BackupSettings, though this data is handled differently from persistent preferences.

Saving and Isolating Device Configuration

The Config::save_device_settings method implements the core isolation logic. This method accepts a DeviceSettings instance and a GeneralSettings instance, then performs an atomic update to the configuration:

  1. Searches the devices vector for an existing entry matching the incoming device_id
  2. If found, replaces the existing entry with the new settings
  3. If not found, appends the new DeviceSettings to the vector
  4. Persists the entire Config to disk

This guarantees that updates to one device never overwrite another device's configuration.

let mut cfg = Config::load_configuration_file();
let dev_settings = DeviceSettings {
    device_id: "ABCD1234".into(),
    disable_mode: true,
    multi_user_mode: false,
    backup: BackupSettings::default(),
};
let general = cfg.general.clone(); // keep existing general settings
cfg.save_device_settings(dev_settings, general);

Loading Configuration and Retrieving Device Settings

The configuration system uses LazyLock to initialize the CONFIG_FILE path exactly once at runtime. The load_configuration_file function safely reads the TOML from this path, automatically falling back to default configuration values if the file is missing or corrupted.

To retrieve settings for a specific device, the application iterates over the devices vector and matches on the device_id:

let cfg = Config::load_configuration_file();
let device_id = "ABCD1234";
let device_cfg = cfg.devices.iter()
    .find(|d| d.device_id == device_id);
if let Some(settings) = device_cfg {
    println!("Disable mode: {}", settings.disable_mode);
}

Separating Volatile State with #[serde(skip)]

Within DeviceSettings, the backup field is marked with #[serde(skip)], which prevents the serialization layer from writing BackupSettings to the TOML file. This isolates volatile runtime data from persistent configuration, ensuring that temporary backup state remains in memory only while static preferences like disable_mode and multi_user_mode are saved to disk.

Shared Core Across GUI and CLI

The isolation mechanism is shared by both front-ends through the core crate. The GUI layer in crates/uad-gui/src/views/settings.rs reads from and writes to Config for the currently selected device, while the CLI implementation in crates/uad-cli/src/device.rs manipulates per-device settings via the same save_device_settings API. This shared architecture ensures consistent device-specific settings isolation regardless of the interface used.

Summary

  • UAD-NG stores all preferences in a single config.toml file, with device-specific settings isolated in a Vec<DeviceSettings> within the Config struct.
  • Each device is uniquely identified by its device_id, and the save_device_settings method ensures updates target only the correct device entry.
  • The #[serde(skip)] attribute on BackupSettings prevents volatile runtime data from being persisted alongside static preferences.
  • The system uses LazyLock for efficient path initialization and provides safe fallbacks to default values when the configuration file is unavailable.
  • Both GUI and CLI front-ends share the same core configuration logic defined in crates/uad-core/src/config.rs, ensuring consistent behavior across interfaces.

Frequently Asked Questions

How does UAD-NG prevent settings from one device affecting another?

UAD-NG maintains a vector of DeviceSettings structs inside the global Config, where each struct contains a unique device_id field. When saving settings, the save_device_settings method searches for an entry with the matching ID and updates only that specific index, ensuring complete isolation between devices.

What happens if I connect a new device that has no saved configuration?

When a new device connects, the application attempts to find its device_id in the devices vector. If no entry exists, the system creates a new DeviceSettings instance with default values and appends it to the vector during the next save operation, automatically initializing configuration for that device without affecting existing entries.

Why are backup settings not saved to the configuration file?

The backup field within DeviceSettings is marked with #[serde(skip)], which instructs the TOML serializer to ignore this field. This design keeps transient backup state in memory only, preventing temporary runtime data from being written to disk and ensuring the configuration file contains only persistent user preferences.

Where is the configuration file stored and how is it loaded?

The configuration file config.toml resides in the application's configuration directory. The system uses a LazyLock-protected CONFIG_FILE constant to compute this path once at runtime. The load_configuration_file function reads this file, but automatically falls back to default configuration values if the file is missing or corrupted, ensuring the application always starts with a valid configuration 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 →