Skip to content

Commit

Permalink
feat: add framebuffer method to USB/TCP direct devices (#69)
Browse files Browse the repository at this point in the history
* feat: store local_id and remote_id in ADBMessageDevice
  • Loading branch information
cli-s1n authored Dec 3, 2024
1 parent 8c382f0 commit 66d1244
Show file tree
Hide file tree
Showing 30 changed files with 465 additions and 376 deletions.
4 changes: 2 additions & 2 deletions adb_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ mod usb;
pub use emu::EmuCommand;
pub use host::{HostCommand, MdnsCommand};
pub use local::LocalCommand;
pub use tcp::{TcpCommand, TcpCommands};
pub use usb::{UsbCommand, UsbCommands};
pub use tcp::TcpCommand;
pub use usb::{DeviceCommands, UsbCommand};
39 changes: 3 additions & 36 deletions adb_cli/src/commands/tcp.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,11 @@
use std::net::SocketAddr;
use std::path::PathBuf;

use clap::Parser;
use std::net::SocketAddr;

use crate::models::RebootTypeCommand;
use super::DeviceCommands;

#[derive(Parser, Debug)]
pub struct TcpCommand {
pub address: SocketAddr,
#[clap(subcommand)]
pub commands: TcpCommands,
}

#[derive(Parser, Debug)]
pub enum TcpCommands {
/// Spawn an interactive shell or run a list of commands on the device
Shell { commands: Vec<String> },
/// Pull a file from device
Pull { source: String, destination: String },
/// Push a file on device
Push { filename: String, path: String },
/// Stat a file on device
Stat { path: String },
/// Run an activity on device specified by the intent
Run {
/// The package whose activity is to be invoked
#[clap(short = 'p', long = "package")]
package: String,
/// The activity to be invoked itself, Usually it is MainActivity
#[clap(short = 'a', long = "activity")]
activity: String,
},
/// Reboot the device
Reboot {
#[clap(subcommand)]
reboot_type: RebootTypeCommand,
},
/// Install an APK on device
Install {
/// Path to APK file. Extension must be ".apk"
path: PathBuf,
},
pub commands: DeviceCommands,
}
9 changes: 7 additions & 2 deletions adb_cli/src/commands/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ pub struct UsbCommand {
#[clap(short = 'k', long = "private-key")]
pub path_to_private_key: Option<PathBuf>,
#[clap(subcommand)]
pub commands: UsbCommands,
pub commands: DeviceCommands,
}

#[derive(Parser, Debug)]
pub enum UsbCommands {
pub enum DeviceCommands {
/// Spawn an interactive shell or run a list of commands on the device
Shell { commands: Vec<String> },
/// Pull a file from device
Expand Down Expand Up @@ -53,4 +53,9 @@ pub enum UsbCommands {
/// Path to APK file. Extension must be ".apk"
path: PathBuf,
},
/// Dump framebuffer of device
Framebuffer {
/// Framebuffer image destination path
path: String,
},
}
32 changes: 17 additions & 15 deletions adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use adb_client::{
};
use anyhow::{anyhow, Result};
use clap::Parser;
use commands::{EmuCommand, HostCommand, LocalCommand, MdnsCommand, TcpCommands, UsbCommands};
use commands::{DeviceCommands, EmuCommand, HostCommand, LocalCommand, MdnsCommand};
use models::{Command, Opts};
use std::fs::File;
use std::io::Write;
Expand Down Expand Up @@ -226,7 +226,7 @@ fn main() -> Result<()> {
};

match usb.commands {
UsbCommands::Shell { commands } => {
DeviceCommands::Shell { commands } => {
if commands.is_empty() {
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
// Using a scope here would call drop() too early..
Expand All @@ -245,42 +245,43 @@ fn main() -> Result<()> {
device.shell_command(commands, std::io::stdout())?;
}
}
UsbCommands::Pull {
DeviceCommands::Pull {
source,
destination,
} => {
let mut output = File::create(Path::new(&destination))?;
device.pull(&source, &mut output)?;
log::info!("Downloaded {source} as {destination}");
}
UsbCommands::Stat { path } => {
DeviceCommands::Stat { path } => {
let stat_response = device.stat(&path)?;
println!("{}", stat_response);
}
UsbCommands::Reboot { reboot_type } => {
DeviceCommands::Reboot { reboot_type } => {
log::info!("Reboots device in mode {:?}", reboot_type);
device.reboot(reboot_type.into())?
}
UsbCommands::Push { filename, path } => {
DeviceCommands::Push { filename, path } => {
let mut input = File::open(Path::new(&filename))?;
device.push(&mut input, &path)?;
log::info!("Uploaded {filename} to {path}");
}
UsbCommands::Run { package, activity } => {
DeviceCommands::Run { package, activity } => {
let output = device.run_activity(&package, &activity)?;
std::io::stdout().write_all(&output)?;
}
UsbCommands::Install { path } => {
DeviceCommands::Install { path } => {
log::info!("Starting installation of APK {}...", path.display());
device.install(path)?;
}
DeviceCommands::Framebuffer { path } => device.framebuffer(path)?,
}
}
Command::Tcp(tcp) => {
let mut device = ADBTcpDevice::new(tcp.address)?;

match tcp.commands {
TcpCommands::Shell { commands } => {
DeviceCommands::Shell { commands } => {
if commands.is_empty() {
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
// Using a scope here would call drop() too early..
Expand All @@ -299,35 +300,36 @@ fn main() -> Result<()> {
device.shell_command(commands, std::io::stdout())?;
}
}
TcpCommands::Pull {
DeviceCommands::Pull {
source,
destination,
} => {
let mut output = File::create(Path::new(&destination))?;
device.pull(&source, &mut output)?;
log::info!("Downloaded {source} as {destination}");
}
TcpCommands::Stat { path } => {
DeviceCommands::Stat { path } => {
let stat_response = device.stat(&path)?;
println!("{}", stat_response);
}
TcpCommands::Reboot { reboot_type } => {
DeviceCommands::Reboot { reboot_type } => {
log::info!("Reboots device in mode {:?}", reboot_type);
device.reboot(reboot_type.into())?
}
TcpCommands::Push { filename, path } => {
DeviceCommands::Push { filename, path } => {
let mut input = File::open(Path::new(&filename))?;
device.push(&mut input, &path)?;
log::info!("Uploaded {filename} to {path}");
}
TcpCommands::Run { package, activity } => {
DeviceCommands::Run { package, activity } => {
let output = device.run_activity(&package, &activity)?;
std::io::stdout().write_all(&output)?;
}
TcpCommands::Install { path } => {
DeviceCommands::Install { path } => {
log::info!("Starting installation of APK {}...", path.display());
device.install(path)?;
}
DeviceCommands::Framebuffer { path } => device.framebuffer(path)?,
}
}
Command::MdnsDiscovery => {
Expand Down
16 changes: 15 additions & 1 deletion adb_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ Add `adb_client` crate as a dependency by simply adding it to your `Cargo.toml`:
adb_client = "*"
```

## Benchmarks

Benchmarks run on `v2.0.6`, on a **Samsung S10 SM-G973F** device and an **Intel i7-1265U** CPU laptop

### `ADBServerDevice` push vs `adb push`

`ADBServerDevice` performs all operations by using adb server as a bridge.

|File size|Sample size|`ADBServerDevice`|`adb`|Difference|
|:-------:|:---------:|:----------:|:---:|:-----:|
|10 MB|100|350,79 ms|356,30 ms|<div style="color:green">-1,57 %</div>|
|500 MB|50|15,60 s|15,64 s|<div style="color:green">-0,25 %</div>|
|1 GB|20|31,09 s|31,12 s|<div style="color:green">-0,10 %</div>|

## Examples

### Get available ADB devices
Expand Down Expand Up @@ -84,7 +98,7 @@ let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
device.push(&mut input, "/data/local/tmp");
```

### (TCP) Get a shell from device
#### (TCP) Get a shell from device

```rust no_run
use std::net::{SocketAddr, IpAddr, Ipv4Addr};
Expand Down
10 changes: 9 additions & 1 deletion adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::{Read, Write};
use std::io::{Read, Seek, Write};
use std::path::Path;

use crate::models::AdbStatResponse;
Expand Down Expand Up @@ -43,4 +43,12 @@ pub trait ADBDeviceExt {

/// Install an APK pointed to by `apk_path` on device.
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()>;

/// Dump framebuffer of this device into given path
fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()>;

/// Dump framebuffer of this device and return corresponding bytes.
///
/// Output data format is currently only `PNG`.
fn framebuffer_bytes<W: Write + Seek>(&mut self, writer: W) -> Result<()>;
}
Loading

0 comments on commit 66d1244

Please sign in to comment.