use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::error::PalpError;
use crate::guard::AnimationGuard;
#[derive(Debug, Clone)]
pub struct Device {
pub serial: String,
pub model: String,
pub state: String,
pub is_emulator: bool,
pub adb_path: PathBuf,
}
impl Device {
pub fn exec_adb(&self, args: &[&str]) -> Result<Vec<u8>, PalpError> {
let mut cmd = Command::new(&self.adb_path);
cmd.arg("-s").arg(&self.serial);
for arg in args {
cmd.arg(arg);
}
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let output = cmd.output().map_err(PalpError::Io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PalpError::AdbFailed(stderr.trim().to_string()));
}
Ok(output.stdout)
}
pub fn shell(&self, command: &str) -> Result<String, PalpError> {
let stdout = self.exec_adb(&["shell", command])?;
Ok(String::from_utf8_lossy(&stdout).trim().to_string())
}
pub fn get_setting(&self, namespace: &str, key: &str) -> Result<String, PalpError> {
let res = self.shell(&format!("settings get {} {}", namespace, key))?;
if res == "null" || res.is_empty() {
Ok("1.0".to_string())
} else {
Ok(res)
}
}
pub fn set_setting(&self, namespace: &str, key: &str, value: &str) -> Result<(), PalpError> {
self.shell(&format!("settings put {} {} {}", namespace, key, value))?;
Ok(())
}
pub fn screencap_png(&self) -> Result<Vec<u8>, PalpError> {
self.exec_adb(&["exec-out", "screencap", "-p"])
}
pub fn dump_ui_xml(&self) -> Result<String, PalpError> {
let _guard = AnimationGuard::acquire(self);
let output = self.shell("uiautomator dump /data/local/tmp/uidump.xml >/dev/null 2>&1 && cat /data/local/tmp/uidump.xml && rm -f /data/local/tmp/uidump.xml")?;
if output.contains("<?xml") {
if let Some(idx) = output.find("<?xml") {
Ok(output[idx..].to_string())
} else {
Ok(output)
}
} else {
Err(PalpError::AdbFailed(format!("Failed to retrieve UI hierarchy:\n{}", output)))
}
}
pub fn tap(&self, x: u32, y: u32) -> Result<(), PalpError> {
self.shell(&format!("input tap {} {}", x, y))?;
Ok(())
}
pub fn swipe(&self, x1: u32, y1: u32, x2: u32, y2: u32, duration_ms: u32) -> Result<(), PalpError> {
self.shell(&format!("input swipe {} {} {} {} {}", x1, y1, x2, y2, duration_ms))?;
Ok(())
}
pub fn input_text(&self, text: &str) -> Result<(), PalpError> {
let sanitized = text
.replace(' ', "%s")
.replace('&', "\\&")
.replace('<', "\\<")
.replace('>', "\\>")
.replace('(', "\\(")
.replace(')', "\\)")
.replace('$', "\\$")
.replace('"', "\\\"")
.replace('\'', "\\'");
self.shell(&format!("input text \"{}\"", sanitized))?;
Ok(())
}
pub fn keyevent(&self, key: &str) -> Result<(), PalpError> {
let binding = key.to_uppercase();
let key_code = match binding.as_str() {
"BACK" => "KEYCODE_BACK",
"HOME" => "KEYCODE_HOME",
"ENTER" => "KEYCODE_ENTER",
"TAB" => "KEYCODE_TAB",
"DEL" | "BACKSPACE" => "KEYCODE_DEL",
"POWER" => "KEYCODE_POWER",
other => other,
};
self.shell(&format!("input keyevent {}", key_code))?;
Ok(())
}
}