# How the Backup and Restore Functionality Works for Debloated Packages in UAD-NG

> Explore how Universal Android Debloater Next Generation backs up and restores debloated packages using timestamped JSON files and ADB commands for seamless recovery.

- Repository: [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
- Tags: internals
- Published: 2026-06-20

---

**Universal Android Debloater Next Generation serializes the state of uninstalled and disabled packages to timestamped JSON files and replays those states via generated ADB commands during restoration.**

The backup and restore functionality for debloated packages in Universal Android Debloater Next Generation (UAD-NG) allows you to preserve your device cleanup progress across factory resets or device swaps. This Rust-based implementation stores per-user package states in human-readable JSON format under configurable paths, then translates those saved states back into executable shell commands when you need to restore. Understanding this mechanism helps you confidently manage device configurations without fear of losing your carefully curated debloating setup.

## Where Backup Data Is Stored

UAD-NG defines backup locations through its global configuration structure. In [`crates/uad-core/src/config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/config.rs), the `Config` struct contains `GeneralSettings` which specifies the root backup directory:

```rust
pub struct GeneralSettings {
    pub backup_folder: PathBuf,          // defaults to CACHE_DIR/backups
    …
}

```

The application resolves this path via `Config::load_configuration_file()` and creates device-specific subdirectories named after the device's ADB ID. For example, a device with ID `emulator-5554` stores its backups at `CACHE_DIR/backups/emulator-5554/`. Each backup file receives a timestamped filename formatted as `%Y-%m-%d_%H-%M-%S.json` to prevent collisions and preserve history.

## Creating a Backup

The backup process begins in the UI layer when the user triggers `Message::BackupDevice`, which delegates to `handle_backup_device` in [`crates/uad-gui/src/views/settings.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/settings.rs). The core logic resides in `backup_phone` within [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs):

```rust
pub fn backup_phone(
    users: Vec<User>,
    device_id: String,
    phone_packages: &[Vec<CorePackage>],
) -> Result<bool, String> {
    // Build serializable structure
    let mut backup = PhoneBackup { device_id, ..Default::default() };
    for u in users {
        let mut user_backup = UserBackup { id: u.id, ..Default::default() };
        // Capture all packages for this user (disabled & uninstalled)
        for p in phone_packages[u.index].iter().cloned() {
            user_backup.packages.push(p);
        }
        backup.users.push(user_backup);
    }

    // Serialize to pretty JSON
    let json = serde_json::to_string_pretty(&backup)?;
    
    // Ensure directory exists and write file
    let backup_dir = Config::load_configuration_file().general.backup_folder;
    let backup_path = backup_dir.join(&device_id);
    fs::create_dir_all(&backup_path)?;
    
    let filename = format!("{}.json", chrono::Local::now().format("%Y-%m-%d_%H-%M-%S"));
    fs::write(backup_path.join(filename), json)?;
    Ok(true)
}

```

The function constructs a `PhoneBackup` structure containing a `Vec<UserBackup>`, where each `UserBackup` holds the user ID and a list of `CorePackage` entries with their saved states (`Uninstalled` or `Disabled`). The resulting JSON is human-readable and contains the full package state snapshot for every user profile on the device.

## Listing and Selecting Backups

When you open the restore interface, UAD-NG populates the dropdown using `list_available_backups` from [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs):

```rust
pub fn list_available_backups(dir: &Path) -> Vec<DisplayablePath> {
    fs::read_dir(dir)
        .ok()
        .into_iter()
        .flatten()
        .map(|e| DisplayablePath { path: e.path() })
        .collect()
}

```

This returns a vector of `DisplayablePath` structs representing available JSON files. Once you select a specific backup file, `list_available_backup_user` parses the JSON to extract user IDs, allowing the UI to present per-user restore options. This step ensures you can selectively restore package states for specific user profiles rather than applying the entire backup blindly.

## The Restore Process

Restoration reverses the backup flow through the `restore_backup` function in [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs). This function deserializes the selected JSON file and reconciles it with the currently connected device:

```rust
pub fn restore_backup(
    selected_device: &Phone,
    packages: &[Vec<CorePackage>],
    settings: &DeviceSettings,
) -> Result<RestoreResult, String> {
    // Load the chosen JSON file
    let data = fs::read_to_string(
        settings.backup.selected.as_ref().ok_or("field should be Some type")?.path.clone()
    )?;
    let phone_backup: PhoneBackup = serde_json::from_str(&data)?;

    // Process each user in the backup
    for u in phone_backup.users {
        // Find matching user on connected device
        let i_user = selected_device.user_list
            .iter()
            .find(|x| x.id == u.id)
            .ok_or_else(|| format!("user {} doesn't exist", u.id))?
            .index;

        // Match packages and generate commands
        for (i, backup_pkg) in u.packages.iter().enumerate() {
            let current_pkg = packages[i_user]
                .iter()
                .find(|x| x.name == backup_pkg.name)
                .ok_or_else(|| {
                    format!("Package {} not found for user {}", backup_pkg.name, u.id)
                })?;

            let cmds = apply_pkg_state_commands(
                &current_pkg,
                backup_pkg.state,
                settings.backup.selected_user.ok_or("field should be Some type")?,
                selected_device,
            );

            if !cmds.is_empty() {
                commands.push(BackupPackage {
                    i_user,
                    index: i,
                    commands: cmds,
                });
            }
        }
    }

    Ok(RestoreResult { packages: commands, skipped_count: skipped_packages })
}

```

The restore logic executes several critical validation steps:

- **User matching**: The code verifies that each `UserBackup.id` exists in `selected_device.user_list`. If a user from the backup is absent on the current device, the function returns an error immediately.
- **Package reconciliation**: For each saved `CorePackage`, the code attempts to locate the corresponding package in the live `packages` list. Missing packages increment a `skipped_count` but do not abort the process.
- **Command generation**: `apply_pkg_state_commands` translates the saved state (`Uninstalled` or `Disabled`) into specific ADB shell commands (e.g., `pm uninstall -k --user <uid> <pkg>` or `pm disable-user <pkg>`).
- **Batch collection**: Commands aggregate into `BackupPackage` structs grouped by user index. The function returns a `RestoreResult` containing these command batches and the count of skipped packages.

The GUI or CLI layer then executes these commands sequentially to recreate the exact debloated state captured in the backup.

## UI Integration and Event Handling

The settings view in [`crates/uad-gui/src/views/settings.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/settings.rs) wires the backup logic to interactive elements:

- **`handle_backup_device`**: Invokes `backup_phone` asynchronously when the user clicks the **Backup** button.
- **`handle_choose_backup_folder`**: Opens a directory dialog to update `general.backup_folder` in the configuration.
- **`backup_restore_container`**: Constructs the interface containing the backup button, restore button, backup file picker, and status text fields (`self.device.backup.backup_state`).

When a user selects a backup file from the dropdown, `handle_backup_selected` updates `self.device.backup.users` using `list_available_backup_user`, enabling granular per-user restoration controls.

## Summary

- **Backup storage**: JSON files written to `CACHE_DIR/backups/<device_id>/<timestamp>.json` as defined in `Config::load_configuration_file()`.
- **Data structure**: Hierarchical `PhoneBackup` → `UserBackup` → `CorePackage` capturing package names and states (`Uninstalled`/`Disabled`).
- **Creation**: `backup_phone` in [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs) serializes current package states with timestamped filenames.
- **Restoration**: `restore_backup` matches users by ID, skips missing packages, and generates ADB commands via `apply_pkg_state_commands`.
- **Error handling**: Restore aborts if a user is missing, but continues if individual packages are absent, reporting the skip count in `RestoreResult`.

## Frequently Asked Questions

### What file format does UAD-NG use for backups?

UAD-NG uses human-readable JSON files generated by `serde_json::to_string_pretty()`. The structure consists of a `PhoneBackup` root object containing device metadata and a list of `UserBackup` entries, each holding package names and their states (`Uninstalled` or `Disabled`). This format allows manual inspection and editing if necessary.

### Where does UAD-NG store backup files by default?

By default, the application stores backups in `CACHE_DIR/backups/<adb_id>/`, where `CACHE_DIR` is the platform-specific cache directory and `<adb_id>` represents the device's unique ADB identifier. You can modify this location through `GeneralSettings.backup_folder` in the configuration or via the UI's "Choose backup folder" dialog.

### What happens if a package from the backup no longer exists on the device?

During restoration, if `restore_backup` encounters a package name in the JSON file that does not exist on the currently connected device, it increments the `skipped_count` and continues processing the remaining packages. This behavior accommodates app updates or manufacturer changes that remove specific system packages between backup and restore operations.

### Can I restore a backup to a different Android device?

You can restore a backup to a different device only if the target device contains the same user profiles (matched by user ID) and the packages referenced in the backup exist on that device. The `restore_backup` function validates user existence strictly and will abort if a user ID from the backup is missing on the new device.