How Device Detection and Connection State Management Works in Universal Android Debloater Next-Generation
The Universal Android Debloater Next-Generation detects Android devices by polling adb devices through a retry-aware wrapper that filters for status "device", then enriches each serial with brand, model, SDK version, and user data to build a comprehensive Phone struct for CLI and GUI operations.
The Universal Android Debloater Next-Generation relies on robust device detection to communicate with Android hardware via ADB. Understanding how this open-source tool manages connection states and discovers attached devices reveals the architecture behind its reliable cross-platform debloating capabilities. This implementation centers on a core sync module that transforms raw ADB output into structured device profiles.
Core Device Detection Architecture
The detection pipeline begins in crates/uad-core/src/sync.rs, where the get_devices_list() function serves as the primary entry point for discovering connected Android devices. This function orchestrates ADB communication, state validation, and metadata enrichment.
Querying ADB for Attached Devices
The low-level ADB interaction resides in crates/uad-core/src/adb.rs. The AdbCommand::devices() method executes the standard adb devices command and parses the tab-delimited output into structured tuples containing serial numbers and their connection statuses.
// adb.rs – devices()
pub fn devices(mut self) -> Result<Vec<(String, String)>, String> {
self.0.arg("devices");
Ok(self
.run()?
.lines()
.skip(1) // header
.map(|dev_stat| {
let tab_idx = dev_stat
.find('\t')
.expect("There must be 1 tab after serial");
(
dev_stat[..tab_idx].to_string(),
dev_stat[(tab_idx + 1)..].to_string(),
)
})
.collect())
}
This method guarantees a one-to-one mapping between Rust methods and ADB commands, returning a Vec<(String, String)> where the first element represents the device serial and the second indicates the connection state (device, unauthorized, offline, etc.).
Retry Logic and Connection State Filtering
The get_devices_list() function implements a retry mechanism using Fixed::from_millis(500) with 10 attempts in release builds and 3 in debug builds. It strictly filters for devices reporting status "device", discarding unauthorized or offline states to ensure only ready devices are processed.
// sync.rs – get_devices_list
pub fn get_devices_list() -> Vec<Phone> {
retry(
Fixed::from_millis(500).take(if cfg!(debug_assertions) { 3 } else { 10 }),
|| match AdbCommand::new().devices() {
Ok(devices) => {
// Only keep devices whose status is "device"
if devices.iter().all(|(_, stat)| stat != "device") {
return OperationResult::Retry(vec![]);
}
let mut device_list = vec![];
for (serial, _) in devices {
device_list.push(Phone {
model: format!("{} {}", get_device_brand(&serial), get_device_model(&serial)),
android_sdk: get_android_sdk(&serial),
user_list: list_users_idx_prot(&serial),
adb_id: serial.clone(),
});
}
OperationResult::Ok(device_list)
}
Err(err) => {
error!("get_devices_list() -> {err}");
OperationResult::Retry(vec![])
}
},
)
.unwrap_or_default()
}
Building the Device Profile
Once a device confirms its ready state, the system collects comprehensive metadata through helper functions defined in sync.rs.
Metadata Extraction via Android Properties
The enrichment process queries device properties via adb shell getprop to populate the Phone struct fields. The system calls get_device_brand() to read ro.product.brand, get_device_model() to read ro.product.model, and get_android_sdk() to parse ro.build.version.sdk into a u8 value.
User Enumeration and Protection Status
The list_users_idx_prot helper executes pm list users to identify protected user accounts, ensuring the debloater respects multi-user Android configurations. This function marks protected users in the returned data structure, preventing accidental modifications to system-critical profiles.
Device Selection Strategies
Selecting a Target Device from the CLI
The CLI layer in crates/uad-cli/src/device.rs provides get_target_device(), which either matches a specific serial number provided via the --device flag or defaults to the first available ready device when multiple connections exist.
// device.rs – get_target_device
let devices = get_devices_list();
if devices.is_empty() {
eprintln!("Error: No devices found");
return Err("No devices found".into());
}
let target_device = if let Some(device_id) = device {
devices.iter()
.find(|d| d.adb_id == device_id)
.ok_or("Device not found")?
.clone()
} else {
// First device is used if multiple are present
devices[0].clone()
};
Ok(target_device)
GUI Integration and Real-Time State Synchronization
The graphical interface in crates/uad-gui/src/gui.rs maintains device state through asynchronous tasks that periodically invoke get_devices_list(). This ensures the device selector reflects current ADB connections without blocking the UI thread.
// gui.rs – device loading task
Task::perform(async { get_devices_list() }, Message::LoadDevices)
When Message::LoadDevices receives the updated device list, the UI refreshes its state to display newly connected devices or remove disconnected ones.
Summary
- Device detection relies on
get_devices_list()incrates/uad-core/src/sync.rsto poll ADB and constructPhonestructs. - Connection state management filters for devices reporting
"device"status, retrying up to 10 times (500ms intervals) to handle transient ADB states. - Metadata enrichment gathers brand, model, SDK version, and user information through property queries and package manager commands.
- CLI selection supports explicit serial targeting or defaults to the first ready device, while the GUI maintains synchronization through asynchronous task polling.
Frequently Asked Questions
How does the Universal Android Debloater handle unauthorized or offline devices?
Devices reporting statuses other than "device" (such as unauthorized or offline) trigger the retry mechanism in get_devices_list(). The function discards these entries and attempts the query again until either ready devices appear or the retry budget exhausts. This prevents the application from attempting operations on devices that cannot yet accept ADB commands.
What information does the Phone struct contain?
The Phone struct contains four primary fields: adb_id (the device serial number), model (concatenated brand and model strings), android_sdk (the API level as a u8), and user_list (a vector of user indices with protection status markers). This structure provides sufficient context for the debloater to display device information and respect multi-user boundaries.
How does the retry mechanism work in device detection?
The retry logic uses a fixed 500-millisecond interval between attempts, allowing 3 tries in debug builds and 10 in release builds. This configuration balances development responsiveness with production stability, accommodating ADB daemon startup delays and USB connection initialization without indefinite blocking.
Can I specify which device to target when multiple are connected?
Yes. The CLI accepts a --device flag followed by the serial number. The get_target_device() function in crates/uad-cli/src/device.rs searches the device list for an exact adb_id match, returning an error if the specified serial is not found among ready devices. Without this flag, the system selects the first device in the list, though users should verify the correct device when multiple Android devices are attached.
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 →