Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions adb_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ anyhow = { version = "1.0.94" }
clap = { version = "4.5.23", features = ["derive"] }
env_logger = { version = "0.11.5" }
log = { version = "0.4.26" }
tabwriter = { version = "1.4.1" }

[target.'cfg(unix)'.dependencies]
termios = { version = "0.3.3" }
Expand Down
42 changes: 38 additions & 4 deletions adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ mod models;
mod utils;

use adb_client::{
ADBDeviceExt, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice, MDNSDiscoveryService,
ADBDeviceExt, ADBDeviceInfo, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice,
MDNSDiscoveryService, find_all_connected_adb_devices,
};

#[cfg(any(target_os = "linux", target_os = "macos"))]
use adb_termios::ADBTermios;

use anyhow::Result;
use anyhow::{Result, bail};
use clap::Parser;
use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands};
use models::{DeviceCommands, LocalCommand, MainCommand, Opts};
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::io::{Write, stdout};
use std::path::Path;
use tabwriter::TabWriter;
use utils::setup_logger;

fn main() -> Result<()> {
Expand Down Expand Up @@ -61,6 +63,34 @@ fn main() -> Result<()> {
}
}
MainCommand::Usb(usb_command) => {
if usb_command.list_devices {
let devices = find_all_connected_adb_devices()?;

let mut writer = TabWriter::new(stdout()).alignment(tabwriter::Alignment::Center);
writeln!(writer, "Index\tVendor ID\tProduct ID\tDevice Description")?;
writeln!(writer, "-----\t---------\t----------\t----------------")?;

for (
index,
ADBDeviceInfo {
vendor_id,
product_id,
device_description,
},
) in devices.iter().enumerate()
{
writeln!(
writer,
"#{}\t{:04x}\t{:04x}\t{}",
index, vendor_id, product_id, device_description
)?;
}

writer.flush()?;

return Ok(());
}

let device = match (usb_command.vendor_id, usb_command.product_id) {
(Some(vid), Some(pid)) => match usb_command.path_to_private_key {
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
Expand All @@ -76,7 +106,11 @@ fn main() -> Result<()> {
);
}
};
(device.boxed(), usb_command.commands)

match usb_command.commands {
Some(commands) => (device.boxed(), commands),
None => bail!("no command provided"),
}
}
MainCommand::Tcp(tcp_command) => {
let device = ADBTcpDevice::new(tcp_command.address)?;
Expand Down
5 changes: 4 additions & 1 deletion adb_cli/src/models/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub struct UsbCommand {
/// Path to a custom private key to use for authentication
#[clap(short = 'k', long = "private-key")]
pub path_to_private_key: Option<PathBuf>,
/// List all connected Android devices
#[clap(short = 'l', long = "list")]
pub list_devices: bool,
#[clap(subcommand)]
pub commands: DeviceCommands,
pub commands: Option<DeviceCommands>,
}
84 changes: 69 additions & 15 deletions adb_client/src/device/adb_usb_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,80 @@ pub fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Optio
}
}

/// Search for adb devices with known interface class and subclass values
pub fn search_adb_devices() -> Result<Option<(u16, u16)>> {
/// Represents an Android device connected via USB
#[derive(Clone, Debug)]
pub struct ADBDeviceInfo {
/// Vendor ID of the device
pub vendor_id: u16,
/// Product ID of the device
pub product_id: u16,
/// Textual description of the device
pub device_description: String,
}

/// Find and return a list of all connected Android devices with known interface class and subclass values
pub fn find_all_connected_adb_devices() -> Result<Vec<ADBDeviceInfo>> {
let mut found_devices = vec![];

for device in rusb::devices()?.iter() {
let Ok(des) = device.device_descriptor() else {
continue;
};

if is_adb_device(&device, &des) {
log::debug!(
"Autodetect device {:04x}:{:04x}",
des.vendor_id(),
des.product_id()
);
found_devices.push((des.vendor_id(), des.product_id()));
let device_handle = match device.open() {
Ok(h) => h,
Err(_) => {
found_devices.push(ADBDeviceInfo {
vendor_id: des.vendor_id(),
product_id: des.product_id(),
device_description: "Unknown device".to_string(),
});
continue;
}
};

let manufacturer = device_handle
.read_manufacturer_string_ascii(&des)
.unwrap_or_else(|_| "Unknown".to_string());

let product = device_handle
.read_product_string_ascii(&des)
.unwrap_or_else(|_| "Unknown".to_string());

let device_description = format!("{} {}", manufacturer, product);

found_devices.push(ADBDeviceInfo {
vendor_id: des.vendor_id(),
product_id: des.product_id(),
device_description,
});
}
}

Ok(found_devices)
}

/// Find and return an USB-connected Android device with known interface class and subclass values.
///
/// Returns the first device found or None if no device is found.
/// If multiple devices are found, an error is returned.
pub fn get_single_connected_adb_device() -> Result<Option<ADBDeviceInfo>> {
let found_devices = find_all_connected_adb_devices()?;
match (found_devices.first(), found_devices.get(1)) {
(None, _) => Ok(None),
(Some(identifiers), None) => Ok(Some(*identifiers)),
(Some((vid1, pid1)), Some((vid2, pid2))) => Err(RustADBError::DeviceNotFound(format!(
"Found two Android devices {vid1:04x}:{pid1:04x} and {vid2:04x}:{pid2:04x}",
(Some(device_info), None) => {
log::debug!(
"Autodetect device {:04x}:{:04x} - {}",
device_info.vendor_id,
device_info.product_id,
device_info.device_description
);
Ok(Some(device_info.clone()))
}
(Some(device_1), Some(device_2)) => Err(RustADBError::DeviceNotFound(format!(
"Found two Android devices {:04x}:{:04x} and {:04x}:{:04x}",
device_1.vendor_id, device_1.product_id, device_2.vendor_id, device_2.product_id
))),
}
}
Expand Down Expand Up @@ -167,10 +219,12 @@ impl ADBUSBDevice {

/// autodetect connected ADB devices and establish a connection with the first device found using a custom private key path
pub fn autodetect_with_custom_private_key(private_key_path: PathBuf) -> Result<Self> {
match search_adb_devices()? {
Some((vendor_id, product_id)) => {
ADBUSBDevice::new_with_custom_private_key(vendor_id, product_id, private_key_path)
}
match get_single_connected_adb_device()? {
Some(device_info) => ADBUSBDevice::new_with_custom_private_key(
device_info.vendor_id,
device_info.product_id,
private_key_path,
),
_ => Err(RustADBError::DeviceNotFound(
"cannot find USB devices matching the signature of an ADB device".into(),
)),
Expand Down
3 changes: 2 additions & 1 deletion adb_client/src/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use adb_message_device::ADBMessageDevice;
pub use adb_tcp_device::ADBTcpDevice;
pub use adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader};
pub use adb_usb_device::{
ADBUSBDevice, get_default_adb_key_path, is_adb_device, search_adb_devices,
ADBDeviceInfo, ADBUSBDevice, find_all_connected_adb_devices, get_default_adb_key_path,
get_single_connected_adb_device, is_adb_device,
};
pub use message_writer::MessageWriter;
pub use models::{ADBRsaKey, MessageCommand, MessageSubcommand};
Expand Down
5 changes: 4 additions & 1 deletion adb_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ mod transports;
mod utils;

pub use adb_device_ext::ADBDeviceExt;
pub use device::{ADBTcpDevice, ADBUSBDevice, is_adb_device, search_adb_devices};
pub use device::{
ADBDeviceInfo, ADBTcpDevice, ADBUSBDevice, find_all_connected_adb_devices,
get_single_connected_adb_device, is_adb_device,
};
pub use emulator_device::ADBEmulatorDevice;
pub use error::{Result, RustADBError};
pub use mdns::*;
Expand Down
Loading