use std::fmt;
use std::process::Command;
pub type FastbootResult<T> = Result<T, FastbootError>;
#[derive(Debug)]
pub enum FastbootError {
NotInstalled,
NoDevice,
CommandFailed(String),
Io(std::io::Error),
}
impl fmt::Display for FastbootError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FastbootError::NotInstalled => write!(
f,
"fastboot not found in PATH.\n\
Install Android platform-tools:\n\
https://developer.android.com/tools/releases/platform-tools"
),
FastbootError::NoDevice => write!(
f,
"No device detected in fastboot mode.\n\n\
Steps:\n\
1. Connect the device via USB\n\
2. Run 'Reboot to Bootloader' from the ROOT & FLASH menu\n\
3. Retry this command"
),
FastbootError::CommandFailed(msg) => write!(f, "{}", msg),
FastbootError::Io(e) => write!(f, "I/O error: {}", e),
}
}
}
impl From<std::io::Error> for FastbootError {
fn from(e: std::io::Error) -> Self {
FastbootError::Io(e)
}
}
#[derive(Debug, Clone)]
pub enum FastbootCommand {
OemUnlock,
OemLock,
WipeData,
FlashPartition {
partition: String,
image_path: String,
},
GetVarAll,
Reboot,
}
impl FastbootCommand {
fn args(&self) -> Vec<String> {
match self {
Self::OemUnlock => vec!["flashing".into(), "unlock".into()],
Self::OemLock => vec!["flashing".into(), "lock".into()],
Self::WipeData => vec!["-w".into()],
Self::GetVarAll => vec!["getvar".into(), "all".into()],
Self::Reboot => vec!["reboot".into()],
Self::FlashPartition {
partition,
image_path,
} => {
vec!["flash".into(), partition.clone(), image_path.clone()]
}
}
}
}
#[derive(Debug, Default)]
pub struct FastbootManager;
impl FastbootManager {
pub fn new() -> Self {
Self
}
pub fn is_available() -> bool {
Command::new("fastboot").arg("--version").output().is_ok()
}
pub fn execute(&self, command: FastbootCommand) -> FastbootResult<String> {
if !Self::is_available() {
return Err(FastbootError::NotInstalled);
}
let output = Command::new("fastboot").args(command.args()).output()?;
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
let body = if stdout.trim().is_empty() {
stderr.clone()
} else {
stdout
};
if stderr.contains("no devices/emulators found") || stderr.contains("no fastboot devices") {
return Err(FastbootError::NoDevice);
}
if !output.status.success() && body.contains("FAILED") {
return Err(FastbootError::CommandFailed(body));
}
Ok(body)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oem_unlock_args() {
assert_eq!(
FastbootCommand::OemUnlock.args(),
vec!["flashing", "unlock"]
);
}
#[test]
fn test_oem_lock_args() {
assert_eq!(FastbootCommand::OemLock.args(), vec!["flashing", "lock"]);
}
#[test]
fn test_wipe_data_args() {
assert_eq!(FastbootCommand::WipeData.args(), vec!["-w"]);
}
#[test]
fn test_get_var_all_args() {
assert_eq!(FastbootCommand::GetVarAll.args(), vec!["getvar", "all"]);
}
#[test]
fn test_reboot_args() {
assert_eq!(FastbootCommand::Reboot.args(), vec!["reboot"]);
}
#[test]
fn test_flash_partition_args() {
let cmd = FastbootCommand::FlashPartition {
partition: "boot".into(),
image_path: "/tmp/boot.img".into(),
};
assert_eq!(cmd.args(), vec!["flash", "boot", "/tmp/boot.img"]);
}
}