How Backup and Restore Functionality Works in UAD-ng: A Technical Deep Dive
UAD-ng stores device debloating states in timestamped JSON files and can re-apply those states to any compatible Android device through a three-layer architecture spanning the UI, core backup logic, and data structures.
The backup and restore functionality in UAD-ng (Universal Android Debloater Next Generation) enables users to capture a snapshot of removed or disabled packages and replicate that configuration across devices. According to the Universal-Debloater-Alliance/universal-android-debloater-next-generation source code, this system relies on JSON serialization, user mapping validation, and dynamic ADB command generation. The implementation separates concerns between the graphical interface, core backup operations, and underlying data structures.
Architecture Overview
The backup system operates across three distinct layers that handle user interaction, data persistence, and device state management.
| Layer | Responsibility | Key Source Files |
|---|---|---|
| UI Layer | Renders backup controls, file pickers, and status messages. | crates/uad-gui/src/views/settings.rs |
| Core Logic | Executes backup creation, directory scanning, and restoration workflows. | crates/uad-core/src/save.rs |
| Data Structures | Defines serializable entities and configuration paths. | crates/uad-core/src/config.rs, crates/uad-core/src/sync.rs |
Creating Device Backups
Data Collection and Serialization
When a user initiates a backup, the system first collects the current device state. The UI layer gathers the list of users (Vec<User>) and per-user package vectors (Vec<CorePackage>) from the sync::Phone struct. It then invokes backup_phone(users, device_id, phone_packages) in crates/uad-core/src/save.rs.
This function constructs a PhoneBackup struct containing the device ID and a UserBackup for each user. Each UserBackup captures only packages marked as Uninstalled or Disabled, filtering out enabled system apps to minimize file size. The serde_json::to_string_pretty method serializes these structures into human-readable JSON.
File Storage and Naming Convention
The backup directory defaults to <cache_dir>/backups as defined in Config::load_configuration_file().general.backup_folder, though users may select custom locations through the GUI. The system creates a subfolder named after the device ID (backup_path = backup_dir.join(device_id)) and generates timestamped filenames using the generate_backup_name helper in utils.rs, producing files like 2024-01-15_14-30-22.json.
use uad_core::save::backup_phone;
use uad_core::sync::{User, CorePackage};
// users: Vec<User> from device sync
// device_id: String (ADB serial number)
// phone_packages: &[Vec<CorePackage>] indexed by user
match backup_phone(users, device_id.clone(), phone_packages) {
Ok(true) => println!("Backup created for device {}", device_id),
Err(e) => eprintln!("Backup failed: {}", e),
}
If any step fails, the function returns a descriptive error string; successful completion yields Ok(true).
Discovering and Selecting Backups
Listing Available Backup Files
The list_available_backups(dir) function in save.rs scans the configured backup directory using fs::read_dir and returns a vector of DisplayablePath wrappers. These paths populate the UI's backup selection dropdown in settings.rs.
Extracting User Information
Before restoration, list_available_backup_user(backup) deserializes the selected JSON file into a PhoneBackup struct and extracts user IDs. This validation ensures the target device contains compatible user profiles before attempting restoration.
use uad_core::save::{list_available_backups, list_available_backup_user};
use uad_core::config::Config;
let backup_dir = Config::load_configuration_file().general.backup_folder;
let backups = list_available_backups(&backup_dir);
for backup in backups {
println!("Available backup: {}", backup.path.display());
// Check which users exist in this backup
if let Ok(users) = list_available_backup_user(&backup) {
println!("Contains {} user profiles", users.len());
}
}
Restoring from Backup
User Mapping and Validation
The restore_backup(selected_device, packages, settings) function in save.rs begins by deserializing the selected backup file. It attempts to map each UserBackup in the file to a corresponding User on the target device using selected_device.user_list. If any backup user lacks a matching target device user, the entire restore operation aborts with an error to prevent partial system modifications.
Package State Reconciliation
For each package stored in the backup, the system locates the current package instance in the device's package matrix (packages[i_user]). Missing packages—those uninstalled or renamed since the backup—are counted and logged but skipped rather than causing failures. This tolerance ensures backups remain usable across ROM updates or device migrations where specific packages may no longer exist.
ADB Command Generation
The apply_pkg_state_commands function generates precise ADB commands to transition each package to its backed-up state (Uninstalled, Disabled, or Enabled). These commands wrap in BackupPackage structs containing the user index, package index, and command list. The UI receives a RestoreResult { packages, skipped_count } and executes the commands through the CLI/ADB layer.
use uad_core::save::restore_backup;
use uad_core::sync::Phone;
// selected_device: &Phone (target device)
// packages: Current package matrix from device
// settings: UI state containing selected backup path
match restore_backup(&selected_device, &packages, &settings) {
Ok(result) => {
for pkg in result.packages {
for cmd in pkg.commands {
// Execute via your ADB runner
run_adb_command(&cmd);
}
}
println!("Restore complete. Skipped {} packages", result.skipped_count);
}
Err(e) => eprintln!("Restore error: {}", e),
}
Configuration and Storage Paths
Backup storage locations are controlled through Config.general.backup_folder in crates/uad-core/src/config.rs. The default location uses the system cache directory, but the Settings view in crates/uad-gui/src/views/settings.rs provides a "Choose backup folder" button that updates this configuration via Message::ChooseBackUpFolder. This path persistence ensures subsequent backup and restore operations use the user-defined directory across application restarts.
Summary
- UAD-ng uses JSON serialization via
serdeto capture debloating states inPhoneBackupandUserBackupstructures. - The core backup logic resides in
crates/uad-core/src/save.rs, implementingbackup_phone,restore_backup, and directory scanning functions. - Timestamped filenames and device-specific subfolders organize backups in the configured
backup_folderdirectory. - Restoration validates user mapping before generating ADB commands, skipping missing packages rather than failing entirely.
- The UI layer in
settings.rshandles user interaction, file selection, and configuration updates through typed message passing.
Frequently Asked Questions
Where does UAD-ng store backup files by default?
By default, UAD-ng stores backups in the system cache directory under a backups subfolder, specifically at CACHE_DIR/backups/<device_id>/. This path is defined in Config.general.backup_folder within crates/uad-core/src/config.rs and can be customized through the Settings interface.
What happens if a package from the backup no longer exists on the device?
The restore process skips missing packages and continues with the remaining entries. The restore_backup function counts these skipped packages and returns the total in RestoreResult.skipped_count, allowing the UI to inform the user without aborting the entire restoration.
Can I restore a backup to a different device than the original?
Yes, provided the target device contains the same user profiles as those stored in the backup. The restoration logic validates user mapping between the PhoneBackup struct and the target device's user_list before applying any changes, returning an error only if user accounts are incompatible.
What package states are included in a backup?
Backups capture only packages marked as Uninstalled or Disabled. Enabled packages are filtered out during the backup_phone execution to minimize file size and focus the backup on modifications made through the debloating process.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →