Error Handling Strategies for ADB Communication Failures in UAD-NG

The Universal Android Debloater Next-Generation codebase implements a three-layer defense that converts raw ADB command-line failures into typed, user-friendly errors through low-level process wrapping, high-level shell interpretation, and domain-specific pattern mapping.

Universal Android Debloater Next-Generation (UAD-NG) orchestrates package management via Android Debug Bridge (ADB), making robust error handling strategies for ADB communication failures essential to maintaining a stable user experience. According to the source code in the Universal-Debloater-Alliance/universal-android-debloater-next-generation repository, the Rust implementation isolates all ADB interactions behind abstraction layers that transform cryptic stderr messages into actionable UI feedback while implementing retry logic and fallback paths for transient hardware glitches.

Three-Layer Error Architecture

The error handling strategy divides responsibilities across three distinct layers, each converting lower-level failures into higher-level context.

Layer 1: Low-Level Process Execution

At the foundation, ACommand::run in crates/uad-core/src/adb.rs (lines 98-115) handles process spawning and raw output capture. This function logs the full ADB command before execution, returning a generic "cannot run ADB" error when the process fails to spawn—such as when the adb binary is missing or permissions are insufficient. On successful execution, it returns trimmed stdout or the raw error string from ADB's stderr.

Layer 2: High-Level Shell Interpretation

The run_adb_shell_action function in crates/uad-core/src/sync.rs (lines 64-86) wraps shell commands and interprets their semantic meaning. Even when ADB returns a successful exit code, this layer scans stdout for the words "Error" or "Failure" to detect logical failures. It implements selective propagation logic: if the error contains the OEM-specific marker "[not installed for …]", the raw string propagates to allow higher layers to handle user-specific recovery; otherwise, it wraps the error as AdbError::Generic with a friendly explanation.

Layer 3: Domain-Specific Error Mapping

The make_friendly_error_message function in crates/uad-core/src/sync.rs (lines 90-138) acts as the final translator, recognizing patterns like Samsung Knox restrictions, missing packages for specific users, empty package names, permission problems, and device policy restrictions. Each pattern transforms into a concise, actionable string suitable for direct display in the GUI or CLI.

Resilience and Recovery Mechanisms

Beyond translation, the codebase implements active recovery strategies for transient and recoverable failures.

Retry Logic for Device Enumeration

The get_devices_list function in crates/uad-core/src/sync.rs (lines 48-74) uses the retry crate with retry::delay::Fixed to poll adb devices up to 10 times in release builds when errors occur. This guards against transient USB or Wi-Fi disconnections, logging each failure attempt before surfacing a permanent error.

Graceful Fallback Paths

When state-change operations fail—such as an uninstall operation that unexpectedly reinstalls—the attempt_fallback function in crates/uad-core/src/sync.rs (lines 86-146) executes an alternate command sequence. It returns a Result<String, String> that bubbles up clear success or failure descriptions, preventing cascading failures from blocking the user workflow.

Error Types and API Design

All ADB-related failures consolidate into the AdbError enum, specifically AdbError::Generic(String), which centralizes handling at call sites. This uniform type ensures that raw error strings never reach the UI directly; instead, they pass through make_friendly_error_message to receive context, remediation tips, and explicit next steps.

Practical Implementation Examples

The following patterns demonstrate how to interact with the error handling system:

use uad_core::sync::{run_adb_shell_action, AdbError};

fn reboot_device(serial: &str) -> Result<(), AdbError> {
    // `run_adb_shell_action` returns a friendly error if anything goes wrong.
    run_adb_shell_action(serial, "reboot")?;
    Ok(())
}

// Example of handling a specific friendly error
match reboot_device("emulator-5554") {
    Ok(_) => println!("Device rebooted successfully."),
    Err(AdbError::Generic(msg)) => {
        eprintln!("Failed to reboot: {}", msg);
        // The message may already contain a tip, e.g. "Permission denied …"
    }
}

For device enumeration with automatic retry:

use uad_core::sync::get_devices_list;

// Enumerate devices with built-in retry logic
let phones = get_devices_list();
if phones.is_empty() {
    eprintln!("No devices found – check USB/Wi-Fi connections.");
} else {
    for phone in phones {
        println!("Found device {} ({})", phone.model, phone.adb_id);
    }
}

Summary

  • Layered abstraction: Errors propagate through ACommand::run, run_adb_shell_action, and make_friendly_error_message before reaching users.
  • Pattern recognition: OEM-specific messages like Samsung Knox or "[not installed for user]" receive specialized handling or pass-through logic.
  • Resilience by default: Device enumeration retries up to 10 times with fixed delays to survive transient disconnections.
  • Graceful degradation: attempt_fallback provides secondary command sequences when primary operations fail.
  • Unified error type: The AdbError::Generic variant ensures consistent error handling across GUI and CLI interfaces.

Frequently Asked Questions

How does UAD-NG handle the "not installed for user" error?

When run_adb_shell_action detects the OEM-specific marker "[not installed for …]" in an error message, it propagates the raw string rather than wrapping it in AdbError::Generic. This allows higher-level code to recognize the error as user-specific and potentially offer user-switching remediation rather than treating it as a generic failure.

What happens when the ADB binary is missing from the system?

In crates/uad-core/src/adb.rs, the ACommand::run function captures process spawn failures and returns a generic "cannot run ADB" error. This occurs before any shell command executes, ensuring the user receives a clear indication that the Android SDK platform tools are not installed or not in the system PATH.

How many retry attempts does UAD-NG make for device enumeration?

The get_devices_list function implements retry logic using the retry crate with a fixed delay between attempts. In release builds, it polls adb devices up to 10 times before failing, logging each transient error to aid in debugging USB or Wi-Fi connectivity issues.

Can UAD-NG recover from failed package uninstall operations?

Yes, the attempt_fallback function in crates/uad-core/src/sync.rs provides recovery paths for state-change operations. If an uninstall operation fails or produces an unexpected result, the code attempts an alternate command sequence and returns a clear success or failure description, preventing the UI from remaining in an inconsistent 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 →