diff --git a/adb_cli/src/main.rs b/adb_cli/src/main.rs index d4e0861..2e7a654 100644 --- a/adb_cli/src/main.rs +++ b/adb_cli/src/main.rs @@ -72,17 +72,21 @@ fn run_command(mut device: Box, command: DeviceCommands) -> AD device.push(&mut input, &path)?; log::info!("Uploaded {filename} to {path}"); } + DeviceCommands::Root => { + device.root()?; + log::info!("Restarted adbd as root"); + } DeviceCommands::Run { package, activity } => { let output = device.run_activity(&package, &activity)?; std::io::stdout().write_all(&output)?; } - DeviceCommands::Install { path } => { + DeviceCommands::Install { path, user } => { log::info!("Starting installation of APK {}...", path.display()); - device.install(&path)?; + device.install(&path, user.as_deref())?; } - DeviceCommands::Uninstall { package } => { + DeviceCommands::Uninstall { package, user } => { log::info!("Uninstalling the package {package}..."); - device.uninstall(&package)?; + device.uninstall(&package, user.as_deref())?; } DeviceCommands::Framebuffer { path } => { device.framebuffer(&path)?; diff --git a/adb_cli/src/models/device.rs b/adb_cli/src/models/device.rs index 9de5f56..915ed73 100644 --- a/adb_cli/src/models/device.rs +++ b/adb_cli/src/models/device.rs @@ -33,11 +33,17 @@ pub enum DeviceCommands { }, /// Install an APK on device Install { + /// User id to install the package for + #[clap(short = 'u', long = "user")] + user: Option, /// Path to APK file. Extension must be ".apk" path: PathBuf, }, /// Uninstall a package from the device Uninstall { + /// User id of the package to uninstall + #[clap(short = 'u', long = "user")] + user: Option, /// Name of the package to uninstall package: String, }, @@ -51,4 +57,6 @@ pub enum DeviceCommands { /// Path to list files from path: String, }, + /// Restart adb daemon with root permissions + Root, } diff --git a/adb_client/src/adb_device_ext.rs b/adb_client/src/adb_device_ext.rs index f8dbac5..5475cde 100644 --- a/adb_client/src/adb_device_ext.rs +++ b/adb_client/src/adb_device_ext.rs @@ -42,6 +42,9 @@ pub trait ADBDeviceExt { /// Remount the device partitions as read-write fn remount(&mut self) -> Result>; + /// Restart adb daemon with root permissions + fn root(&mut self) -> Result<()>; + /// Run `activity` from `package` on device. Return the command output. fn run_activity( &mut self, @@ -63,10 +66,10 @@ pub trait ADBDeviceExt { } /// Install an APK pointed to by `apk_path` on device. - fn install(&mut self, apk_path: &dyn AsRef) -> Result<()>; + fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()>; /// Uninstall the package `package` from device. - fn uninstall(&mut self, package: &dyn AsRef) -> Result<()>; + fn uninstall(&mut self, package: &dyn AsRef, user: Option<&str>) -> Result<()>; /// Enable dm-verity on the device fn enable_verity(&mut self) -> Result<()>; diff --git a/adb_client/src/message_devices/adb_message_device_commands.rs b/adb_client/src/message_devices/adb_message_device_commands.rs index 58cb959..5cfb6dd 100644 --- a/adb_client/src/message_devices/adb_message_device_commands.rs +++ b/adb_client/src/message_devices/adb_message_device_commands.rs @@ -57,13 +57,18 @@ impl ADBDeviceExt for ADBMessageDevice { } #[inline] - fn install(&mut self, apk_path: &dyn AsRef) -> Result<()> { - self.install(apk_path) + fn root(&mut self) -> Result<()> { + self.root() } #[inline] - fn uninstall(&mut self, package: &dyn AsRef) -> Result<()> { - self.uninstall(package) + fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.install(apk_path, user) + } + + #[inline] + fn uninstall(&mut self, package: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.uninstall(package, user) } #[inline] diff --git a/adb_client/src/message_devices/commands/install.rs b/adb_client/src/message_devices/commands/install.rs index 841cc17..0ab57c4 100644 --- a/adb_client/src/message_devices/commands/install.rs +++ b/adb_client/src/message_devices/commands/install.rs @@ -11,14 +11,17 @@ use crate::{ }; impl ADBMessageDevice { - pub(crate) fn install(&mut self, apk_path: &dyn AsRef) -> Result<()> { + pub(crate) fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()> { let mut apk_file = File::open(apk_path)?; check_extension_is_apk(apk_path)?; let file_size = apk_file.metadata()?.len(); - let mut session = self.open_session(&ADBLocalCommand::Install(file_size))?; + let mut session = self.open_session(&ADBLocalCommand::Install( + file_size, + user.map(ToString::to_string), + ))?; { // Read data from apk_file and write it to the underlying session diff --git a/adb_client/src/message_devices/commands/mod.rs b/adb_client/src/message_devices/commands/mod.rs index 9d0cf1d..6401881 100644 --- a/adb_client/src/message_devices/commands/mod.rs +++ b/adb_client/src/message_devices/commands/mod.rs @@ -5,6 +5,7 @@ mod pull; mod push; mod reboot; mod remount; +mod root; mod shell; mod stat; mod uninstall; diff --git a/adb_client/src/message_devices/commands/root.rs b/adb_client/src/message_devices/commands/root.rs new file mode 100644 index 0000000..424912f --- /dev/null +++ b/adb_client/src/message_devices/commands/root.rs @@ -0,0 +1,18 @@ +use crate::{ + Result, + message_devices::{ + adb_message_device::ADBMessageDevice, adb_message_transport::ADBMessageTransport, + message_commands::MessageCommand, + }, + models::ADBLocalCommand, +}; + +impl ADBMessageDevice { + pub(crate) fn root(&mut self) -> Result<()> { + self.open_session(&ADBLocalCommand::Root)?; + + self.get_transport_mut() + .read_message() + .and_then(|message| message.assert_command(MessageCommand::Okay)) + } +} diff --git a/adb_client/src/message_devices/commands/uninstall.rs b/adb_client/src/message_devices/commands/uninstall.rs index 3cf1c10..00c9eb8 100644 --- a/adb_client/src/message_devices/commands/uninstall.rs +++ b/adb_client/src/message_devices/commands/uninstall.rs @@ -7,9 +7,14 @@ use crate::{ }; impl ADBMessageDevice { - pub(crate) fn uninstall(&mut self, package_name: &dyn AsRef) -> Result<()> { + pub(crate) fn uninstall( + &mut self, + package_name: &dyn AsRef, + user: Option<&str>, + ) -> Result<()> { self.open_session(&ADBLocalCommand::Uninstall( package_name.as_ref().to_string(), + user.map(ToString::to_string), ))?; let final_status = self.get_transport_mut().read_message()?; diff --git a/adb_client/src/message_devices/tcp/adb_tcp_device.rs b/adb_client/src/message_devices/tcp/adb_tcp_device.rs index e8751fa..4444bba 100644 --- a/adb_client/src/message_devices/tcp/adb_tcp_device.rs +++ b/adb_client/src/message_devices/tcp/adb_tcp_device.rs @@ -137,13 +137,18 @@ impl ADBDeviceExt for ADBTcpDevice { } #[inline] - fn install(&mut self, apk_path: &dyn AsRef) -> Result<()> { - self.inner.install(apk_path) + fn root(&mut self) -> Result<()> { + self.inner.root() } #[inline] - fn uninstall(&mut self, package: &dyn AsRef) -> Result<()> { - self.inner.uninstall(package) + fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.inner.install(apk_path, user) + } + + #[inline] + fn uninstall(&mut self, package: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.inner.uninstall(package, user) } #[inline] diff --git a/adb_client/src/message_devices/usb/adb_usb_device.rs b/adb_client/src/message_devices/usb/adb_usb_device.rs index 6a0bc9a..25cd81d 100644 --- a/adb_client/src/message_devices/usb/adb_usb_device.rs +++ b/adb_client/src/message_devices/usb/adb_usb_device.rs @@ -271,13 +271,18 @@ impl ADBDeviceExt for ADBUSBDevice { } #[inline] - fn install(&mut self, apk_path: &dyn AsRef) -> Result<()> { - self.inner.install(apk_path) + fn root(&mut self) -> Result<()> { + self.inner.root() } #[inline] - fn uninstall(&mut self, package: &dyn AsRef) -> Result<()> { - self.inner.uninstall(package) + fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.inner.install(apk_path, user) + } + + #[inline] + fn uninstall(&mut self, package: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.inner.uninstall(package, user) } #[inline] diff --git a/adb_client/src/models/adb_local_command.rs b/adb_client/src/models/adb_local_command.rs index 231978d..d145f1d 100644 --- a/adb_client/src/models/adb_local_command.rs +++ b/adb_client/src/models/adb_local_command.rs @@ -18,10 +18,11 @@ pub(crate) enum ADBLocalCommand { Remount, DisableVerity, EnableVerity, - Uninstall(String), - Install(u64), + Uninstall(String, Option), + Install(u64, Option), TcpIp(u16), Usb, + Root, } impl Display for ADBLocalCommand { @@ -44,11 +45,21 @@ impl Display for ADBLocalCommand { Self::Reboot(reboot_type) => { write!(f, "reboot:{reboot_type}") } - Self::Uninstall(package) => { - write!(f, "exec:cmd package 'uninstall' {package}") + Self::Uninstall(package, user) => { + write!(f, "exec:cmd package 'uninstall'")?; + if let Some(user) = user { + write!(f, " --user {user}")?; + } + write!(f, " {package}") } Self::FrameBuffer => write!(f, "framebuffer:"), - Self::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"), + Self::Install(size, user) => { + write!(f, "exec:cmd package 'install'")?; + if let Some(user) = user { + write!(f, " --user {user}")?; + } + write!(f, " -S {size}") + } Self::Forward(remote, local) => { write!(f, "host:forward:{local};{remote}") } @@ -65,6 +76,7 @@ impl Display for ADBLocalCommand { write!(f, "tcpip:{port}") } Self::Usb => write!(f, "usb:"), + Self::Root => write!(f, "root:"), } } } diff --git a/adb_client/src/server/adb_server.rs b/adb_client/src/server/adb_server.rs index fde3c5d..0056687 100644 --- a/adb_client/src/server/adb_server.rs +++ b/adb_client/src/server/adb_server.rs @@ -43,6 +43,12 @@ impl ADBServer { } } + /// Get the server address + #[must_use] + pub fn socket_addr(&self) -> Option { + self.socket_addr + } + /// Start an instance of `adb-server` pub fn start(envs: &HashMap, adb_path: &Option) { // ADB Server is local, we start it if not already running diff --git a/adb_client/src/server/commands/connect.rs b/adb_client/src/server/commands/connect.rs index 0ae4e8d..a969661 100644 --- a/adb_client/src/server/commands/connect.rs +++ b/adb_client/src/server/commands/connect.rs @@ -14,6 +14,7 @@ impl ADBServer { match String::from_utf8(response) { Ok(s) if s.starts_with("connected to") => Ok(()), + Ok(s) if s.starts_with("already connected to") => Ok(()), Ok(s) => Err(RustADBError::ADBRequestFailed(s)), Err(e) => Err(e.into()), } diff --git a/adb_client/src/server_device/adb_server_device_commands.rs b/adb_client/src/server_device/adb_server_device_commands.rs index 7921838..5d822b7 100644 --- a/adb_client/src/server_device/adb_server_device_commands.rs +++ b/adb_client/src/server_device/adb_server_device_commands.rs @@ -90,16 +90,20 @@ impl ADBDeviceExt for ADBServerDevice { self.reboot(reboot_type) } + fn root(&mut self) -> Result<()> { + self.root() + } + fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef) -> Result<()> { self.push(stream, path) } - fn install(&mut self, apk_path: &dyn AsRef) -> Result<()> { - self.install(apk_path) + fn install(&mut self, apk_path: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.install(apk_path, user) } - fn uninstall(&mut self, package: &dyn AsRef) -> Result<()> { - self.uninstall(package.as_ref()) + fn uninstall(&mut self, package: &dyn AsRef, user: Option<&str>) -> Result<()> { + self.uninstall(package.as_ref(), user) } fn framebuffer_inner(&mut self) -> Result, Vec>> { diff --git a/adb_client/src/server_device/commands/install.rs b/adb_client/src/server_device/commands/install.rs index a4fc601..64a6674 100644 --- a/adb_client/src/server_device/commands/install.rs +++ b/adb_client/src/server_device/commands/install.rs @@ -9,7 +9,7 @@ use crate::{ impl ADBServerDevice { /// Install an APK on device - pub fn install>(&mut self, apk_path: P) -> Result<()> { + pub fn install>(&mut self, apk_path: P, user: Option<&str>) -> Result<()> { let mut apk_file = File::open(&apk_path)?; check_extension_is_apk(&apk_path)?; @@ -19,7 +19,10 @@ impl ADBServerDevice { self.set_serial_transport()?; self.transport - .send_adb_request(&ADBCommand::Local(ADBLocalCommand::Install(file_size)))?; + .send_adb_request(&ADBCommand::Local(ADBLocalCommand::Install( + file_size, + user.map(ToString::to_string), + )))?; let mut raw_connection = self.transport.get_raw_connection()?; diff --git a/adb_client/src/server_device/commands/mod.rs b/adb_client/src/server_device/commands/mod.rs index 6f3397c..31cf150 100644 --- a/adb_client/src/server_device/commands/mod.rs +++ b/adb_client/src/server_device/commands/mod.rs @@ -9,6 +9,7 @@ mod reconnect; mod recv; mod remount; mod reverse; +mod root; mod send; mod stat; mod tcpip; diff --git a/adb_client/src/server_device/commands/root.rs b/adb_client/src/server_device/commands/root.rs new file mode 100644 index 0000000..9a025ff --- /dev/null +++ b/adb_client/src/server_device/commands/root.rs @@ -0,0 +1,16 @@ +use crate::{ + Result, + models::{ADBCommand, ADBLocalCommand}, + server_device::ADBServerDevice, +}; + +impl ADBServerDevice { + /// Restart adb daemon with root permissions + pub fn root(&mut self) -> Result<()> { + self.set_serial_transport()?; + + self.transport + .proxy_connection(&ADBCommand::Local(ADBLocalCommand::Root), false) + .map(|_| ()) + } +} diff --git a/adb_client/src/server_device/commands/uninstall.rs b/adb_client/src/server_device/commands/uninstall.rs index f2a5914..12d1112 100644 --- a/adb_client/src/server_device/commands/uninstall.rs +++ b/adb_client/src/server_device/commands/uninstall.rs @@ -8,12 +8,13 @@ use crate::{ impl ADBServerDevice { /// Uninstall a package from device - pub fn uninstall(&mut self, package_name: &str) -> Result<()> { + pub fn uninstall(&mut self, package_name: &str, user: Option<&str>) -> Result<()> { self.set_serial_transport()?; self.transport .send_adb_request(&ADBCommand::Local(ADBLocalCommand::Uninstall( package_name.to_string(), + user.map(ToString::to_string), )))?; let mut data = [0; 1024]; diff --git a/pyadb_client/README.md b/pyadb_client/README.md index 4c65150..ecac2da 100644 --- a/pyadb_client/README.md +++ b/pyadb_client/README.md @@ -34,6 +34,21 @@ device = server.get_device() print(device, device.identifier) ``` +### Connect and Disconnect with device id + +```python +from pyadb_client import PyADBServer + +server = PyADBServer("127.0.0.1:5037") + +# Connect to a device with device id +device = server.connect_device("192.168.1.100:5555") +print(f"Connected to {device.identifier}") + +# Disconnect from a device with device id +server.disconnect_device("192.168.1.100:5555") +``` + ### Push a file on device ```python diff --git a/pyadb_client/src/adb_server.rs b/pyadb_client/src/adb_server.rs index c1a1705..ef2cd3a 100644 --- a/pyadb_client/src/adb_server.rs +++ b/pyadb_client/src/adb_server.rs @@ -1,6 +1,6 @@ use std::net::SocketAddrV4; -use adb_client::server::ADBServer; +use adb_client::{server::ADBServer, server_device::ADBServerDevice}; use anyhow::Result; use pyo3::{PyResult, pyclass, pymethods}; use pyo3_stub_gen_derive::{gen_stub_pyclass, gen_stub_pymethods}; @@ -41,6 +41,19 @@ impl PyADBServer { pub fn get_device_by_name(&mut self, name: &str) -> Result { Ok(self.0.get_device_by_name(name)?.into()) } + + /// Connect device over tcp with address and port + pub fn connect_device(&mut self, address: String) -> Result { + let socket_address = address.parse::()?; + self.0.connect_device(socket_address)?; + Ok(ADBServerDevice::new(address, self.0.socket_addr()).into()) + } + + /// Disconnect device over tcp with address and port + pub fn disconnect_device(&mut self, address: &str) -> Result<()> { + let socket_address = address.parse::()?; + Ok(self.0.disconnect_device(socket_address)?) + } } impl From for PyADBServer { diff --git a/pyadb_client/src/adb_server_device.rs b/pyadb_client/src/adb_server_device.rs index 7f56bb5..5bbe286 100644 --- a/pyadb_client/src/adb_server_device.rs +++ b/pyadb_client/src/adb_server_device.rs @@ -42,13 +42,20 @@ impl PyADBServerDevice { /// Install a package installed on the device #[expect(clippy::needless_pass_by_value)] - pub fn install(&mut self, apk_path: PathBuf) -> Result<()> { - Ok(self.0.install(&apk_path)?) + #[pyo3(signature = (apk_path, user=None))] + pub fn install(&mut self, apk_path: PathBuf, user: Option<&str>) -> Result<()> { + Ok(self.0.install(&apk_path, user)?) } /// Uninstall a package installed on the device - pub fn uninstall(&mut self, package: &str) -> Result<()> { - Ok(self.0.uninstall(package)?) + #[pyo3(signature = (package, user=None))] + pub fn uninstall(&mut self, package: &str, user: Option<&str>) -> Result<()> { + Ok(self.0.uninstall(package, user)?) + } + + /// Restart adb daemon with root permissions + pub fn root(&mut self) -> Result<()> { + Ok(self.0.root()?) } } diff --git a/pyadb_client/src/adb_usb_device.rs b/pyadb_client/src/adb_usb_device.rs index 505bf7d..13c4784 100644 --- a/pyadb_client/src/adb_usb_device.rs +++ b/pyadb_client/src/adb_usb_device.rs @@ -43,13 +43,20 @@ impl PyADBUSBDevice { /// Install a package installed on the device #[expect(clippy::needless_pass_by_value)] - pub fn install(&mut self, apk_path: PathBuf) -> Result<()> { - Ok(self.0.install(&apk_path)?) + #[pyo3(signature = (apk_path, user=None))] + pub fn install(&mut self, apk_path: PathBuf, user: Option<&str>) -> Result<()> { + Ok(self.0.install(&apk_path, user)?) } /// Uninstall a package installed on the device - pub fn uninstall(&mut self, package: &str) -> Result<()> { - Ok(self.0.uninstall(&package)?) + #[pyo3(signature = (package, user=None))] + pub fn uninstall(&mut self, package: &str, user: Option<&str>) -> Result<()> { + Ok(self.0.uninstall(&package, user)?) + } + + /// Restart adb daemon with root permissions + pub fn root(&mut self) -> Result<()> { + Ok(self.0.root()?) } }