use std::collections::HashSet;
use std::time::Duration;
use futures_core::Stream;
use crate::conn::AdbConnection;
use crate::device::AdbDevice;
use crate::errors::{AdbError, Result};
use crate::proto::{AdbDeviceInfo, DeviceEvent, ForwardItem};
#[derive(Debug, Clone)]
pub struct AdbClient {
host: String,
port: u16,
socket_timeout: Option<Duration>,
}
impl Default for AdbClient {
fn default() -> Self {
let host = std::env::var("ANDROID_ADB_SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".into());
let port = std::env::var("ANDROID_ADB_SERVER_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(5037);
Self { host, port, socket_timeout: None }
}
}
impl AdbClient {
pub fn new(host: impl Into<String>, port: u16, socket_timeout: Option<Duration>) -> Self {
Self { host: host.into(), port, socket_timeout }
}
pub fn host(&self) -> &str {
&self.host
}
pub fn port(&self) -> u16 {
self.port
}
pub async fn make_connection(&self, timeout: Option<Duration>) -> Result<AdbConnection> {
let timeout = timeout.or(self.socket_timeout);
AdbConnection::connect(&self.host, self.port, timeout).await
}
pub async fn server_version(&self) -> Result<u32> {
let mut c = self.make_connection(None).await?;
c.send_command("host:version").await?;
c.check_okay().await?;
let block = c.read_string_block().await?;
u32::from_str_radix(block.trim(), 16)
.map_err(|_| AdbError::adb(format!("bad version block: {block:?}")))
}
pub async fn server_kill(&self) -> Result<()> {
if self.make_connection(Some(Duration::from_millis(100))).await.is_err() {
return Ok(());
}
let mut c = self.make_connection(None).await?;
c.send_command("host:kill").await?;
c.check_okay().await
}
pub async fn wait_for(
&self,
serial: Option<&str>,
transport: &str,
state: &str,
timeout: Duration,
) -> Result<()> {
let mut c = self.make_connection(Some(timeout)).await?;
let cmd = match serial {
Some(s) => format!("host-serial:{s}:wait-for-{transport}-{state}"),
None => format!("host:wait-for-{transport}-{state}"),
};
c.send_command(&cmd).await?;
c.check_okay().await?;
c.check_okay().await
}
pub async fn connect(&self, addr: &str, timeout: Option<Duration>) -> Result<String> {
let mut c = self.make_connection(timeout).await?;
c.send_command(&format!("host:connect:{addr}")).await?;
c.check_okay().await?;
c.read_string_block().await
}
pub async fn disconnect(&self, addr: &str, raise_error: bool) -> Result<Option<String>> {
let res = async {
let mut c = self.make_connection(None).await?;
c.send_command(&format!("host:disconnect:{addr}")).await?;
c.check_okay().await?;
c.read_string_block().await
}
.await;
match res {
Ok(s) => Ok(Some(s)),
Err(e) if raise_error => Err(e),
Err(_) => Ok(None),
}
}
pub async fn forward_list(&self, serial: Option<&str>) -> Result<Vec<ForwardItem>> {
let mut c = self.make_connection(None).await?;
let cmd = match serial {
Some(s) => format!("host-serial:{s}:list-forward"),
None => "host:list-forward".to_string(),
};
c.send_command(&cmd).await?;
c.check_okay().await?;
let content = c.read_string_block().await?;
let mut items = Vec::new();
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 3 {
continue;
}
if let Some(s) = serial {
if parts[0] != s {
continue;
}
}
items.push(ForwardItem {
serial: parts[0].to_string(),
local: parts[1].to_string(),
remote: parts[2].to_string(),
});
}
Ok(items)
}
pub async fn forward(
&self,
serial: &str,
local: &str,
remote: &str,
norebind: bool,
) -> Result<()> {
let mut c = self.make_connection(None).await?;
let rebind = if norebind { "forward:norebind:" } else { "forward:" };
c.send_command(&format!("host-serial:{serial}:{rebind}{local};{remote}"))
.await?;
c.check_okay().await
}
pub async fn list(&self, extended: bool) -> Result<Vec<AdbDeviceInfo>> {
let mut c = self.make_connection(None).await?;
c.send_command(if extended { "host:devices-l" } else { "host:devices" })
.await?;
c.check_okay().await?;
let output = c.read_string_block().await?;
let mut infos = Vec::new();
for line in output.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
continue;
}
let mut tags = std::collections::HashMap::new();
if extended {
for part in &parts[2..] {
if let Some((k, v)) = part.split_once(':') {
tags.insert(k.to_string(), v.to_string());
}
}
}
infos.push(AdbDeviceInfo {
serial: parts[0].to_string(),
state: parts[1].to_string(),
tags,
});
}
Ok(infos)
}
pub async fn device_list(&self) -> Result<Vec<AdbDevice>> {
let infos = self.list(false).await?;
Ok(infos
.into_iter()
.filter(|i| i.state == "device")
.map(|i| AdbDevice::with_serial(self.clone(), i.serial))
.collect())
}
pub fn device(&self, serial: impl Into<String>) -> AdbDevice {
AdbDevice::with_serial(self.clone(), serial.into())
}
pub fn device_with_transport_id(&self, transport_id: i64) -> AdbDevice {
AdbDevice::with_transport_id(self.clone(), transport_id)
}
pub async fn any_device(&self) -> Result<AdbDevice> {
if let Ok(serial) = std::env::var("ANDROID_SERIAL") {
if !serial.is_empty() {
return Ok(self.device(serial));
}
}
let mut ds = self.device_list().await?;
match ds.len() {
0 => Err(AdbError::adb("Can't find any android device/emulator")),
1 => Ok(ds.pop().unwrap()),
_ => Err(AdbError::adb(
"more than one device/emulator, please specify the serial number",
)),
}
}
pub fn track_devices(&self) -> impl Stream<Item = Result<DeviceEvent>> + '_ {
async_stream::try_stream! {
let mut c = self.make_connection(None).await?;
c.send_command("host:track-devices").await?;
c.check_okay().await?;
let mut orig: Vec<DeviceEvent> = Vec::new();
loop {
let output = c.read_string_block().await?;
let curr = output2devices(&output);
for ev in diff_devices(&orig, &curr) {
yield ev;
}
orig = curr;
}
}
}
}
fn output2devices(output: &str) -> Vec<DeviceEvent> {
let mut devices = Vec::new();
for line in output.lines() {
let line = line.trim();
if let Some((serial, status)) = line.split_once('\t') {
devices.push(DeviceEvent {
present: true,
serial: serial.to_string(),
status: status.to_string(),
});
}
}
devices
}
fn diff_devices(orig: &[DeviceEvent], curr: &[DeviceEvent]) -> Vec<DeviceEvent> {
let key = |d: &DeviceEvent| (d.serial.clone(), d.status.clone());
let orig_set: HashSet<_> = orig.iter().map(key).collect();
let curr_set: HashSet<_> = curr.iter().map(key).collect();
let mut events = Vec::new();
for d in orig {
if !curr_set.contains(&key(d)) {
events.push(DeviceEvent {
present: false,
serial: d.serial.clone(),
status: "absent".to_string(),
});
}
}
for d in curr {
if !orig_set.contains(&key(d)) {
events.push(DeviceEvent {
present: true,
serial: d.serial.clone(),
status: d.status.clone(),
});
}
}
events
}