rust_droid/
lib.rs

1//! A simple and fluent Android automation library for Rust.
2//!
3//! `rust-droid` provides a high-level API to control Android devices via ADB
4//! (Android Debug Bridge). It is inspired by popular automation tools like
5//! Airtest, with a focus on ease of use and a fluent, builder-style API.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use rust_droid::{Droid, DroidConfig, Target, models::KeyCode};
11//! use std::path::PathBuf;
12//! use std::time::Duration;
13//!
14//! fn main() -> anyhow::Result<()> {
15//!     // 1. Create a Droid instance.
16//!     let mut droid = Droid::new(DroidConfig::default())?;
17//!
18//!     // 2. Define a target image.
19//!     let settings_icon = Target::from("path/to/your/settings_icon.png");
20//!
21//!     // 3. Wait for the icon to appear and get its position.
22//!     let icon_position = droid.wait_for(settings_icon).execute()?;
23//!
24//!     // 4. Tap the icon.
25//!     droid.touch(icon_position.into()).execute()?;
26//!
27//!     // 5. Press the back button.
28//!     droid.keyevent(KeyCode::Back).execute()?;
29//!
30//!     Ok(())
31//! }
32//! ```
33
34pub mod action;
35pub mod common;
36pub mod config;
37pub mod device;
38pub mod error;
39pub mod models;
40pub mod vision;
41
42use crate::common::point::Point;
43use crate::common::rect::Rect;
44use crate::common::relative_rect::RelativeRect;
45use crate::models::KeyCode;
46pub use config::DroidConfig;
47use device::DeviceController;
48use error::{DroidError, Result};
49use image::{DynamicImage, GenericImageView};
50pub use models::Target;
51use std::path::Path;
52use std::time::Duration;
53
54/// The main entry point for interacting with an Android device.
55///
56/// The `Droid` struct holds the connection to a device and provides methods
57/// for performing actions like tapping, swiping, and image recognition.
58pub struct Droid {
59    controller: DeviceController,
60    pub(crate) config: DroidConfig,
61}
62
63impl Droid {
64    /// Creates a new `Droid` instance and connects to a device.
65    pub fn new(config: DroidConfig) -> Result<Self> {
66        let controller =
67            DeviceController::new(config.device_serial.as_deref(), config.adb_server_addr)?;
68        Ok(Self { controller, config })
69    }
70
71    pub(crate) fn resolve_target(
72        &mut self,
73        target: &Target,
74        threshold: f32,
75        search_rect: Option<RelativeRect>,
76    ) -> Result<Point> {
77        match target {
78            Target::Point(p) => {
79                if search_rect.is_some() {
80                    log::warn!("Search region is ignored when the target is a Point.");
81                }
82                log::debug!("Target resolved to a direct point: {:?}", p);
83                Ok(*p)
84            }
85            Target::Image(path) => {
86                log::debug!("Attempting to resolve image target: {:?}", path);
87                let needle = image::open(path)?;
88                let haystack = self.controller.screenshot()?;
89
90                let absolute_search_rect: Option<Rect> = search_rect.map(|relative_rect| {
91                    let (w, h) = haystack.dimensions();
92                    relative_rect.to_absolute(w, h)
93                });
94
95                let match_result = vision::find_template(
96                    &haystack,
97                    &needle,
98                    threshold,
99                    path,
100                    absolute_search_rect,
101                )?;
102
103                let center_point = match_result.rect.center();
104                log::info!(
105                    "Image target found at {:?}, center: {:?}, confidence: {:.4}",
106                    match_result.rect,
107                    center_point,
108                    match_result.confidence
109                );
110                Ok(center_point)
111            }
112        }
113    }
114
115    /// Initiates a touch action on a target.
116    ///
117    /// Returns a `TouchBuilder` to configure and execute the action.
118    ///
119    /// # Example
120    ///
121    /// ```no_run
122    /// # use rust_droid::{Droid, DroidConfig, Target};
123    /// # let mut droid = Droid::new(DroidConfig::default()).unwrap();
124    /// # let target = Target::from("path/to/image.png");
125    /// // Tap an image target twice.
126    /// droid.touch(target).times(2).execute()?;
127    /// # Ok::<(), anyhow::Error>(())
128    /// ```
129    pub fn touch(&mut self, target: Target) -> action::touch::TouchBuilder<'_> {
130        action::touch::TouchBuilder::new(self, target)
131    }
132
133    /// Initiates a swipe action between two targets.
134    ///
135    /// Returns a `SwipeBuilder` to configure and execute the action.
136    pub fn swipe(&mut self, start: Target, end: Target) -> action::swipe::SwipeBuilder<'_> {
137        action::swipe::SwipeBuilder::new(self, start, end)
138    }
139
140    /// Waits for a target to appear on the screen.
141    ///
142    /// Returns a `WaitBuilder` to configure timeouts and execute the wait operation.
143    /// The operation succeeds by returning the `Point` where the target was found.
144    pub fn wait_for(&mut self, target: Target) -> action::wait::WaitBuilder<'_> {
145        action::wait::WaitBuilder::new(self, target)
146    }
147
148    /// Initiates a text input action.
149    ///
150    /// Returns a `TextBuilder` to execute the action.
151    pub fn text(&mut self, text: &str) -> action::text::TextBuilder<'_> {
152        action::text::TextBuilder::new(self, text)
153    }
154
155    /// Pauses the script execution for a specified duration.
156    pub fn sleep(&self, duration: Duration) {
157        log::info!("Sleeping for {:?}", duration);
158        std::thread::sleep(duration);
159    }
160
161    /// Takes a screenshot of the current device screen and returns it as an image object.
162    ///
163    /// This is the programmatic alternative to `snapshot`, which saves the image to a file.
164    ///
165    /// # Returns
166    ///
167    /// A `Result` containing an `image::DynamicImage` on success.
168    pub fn screenshot(&mut self) -> Result<DynamicImage> {
169        self.controller.screenshot()
170    }
171
172    /// Takes a screenshot of the current device screen and saves it to a file.
173    ///
174    /// # Arguments
175    ///
176    /// * `path` - The path where the screenshot image will be saved.
177    pub fn snapshot<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
178        let path_ref = path.as_ref();
179        log::info!("Saving snapshot to {:?}", path_ref);
180        let image = self.screenshot()?;
181        image.save(path_ref)?;
182        Ok(())
183    }
184
185    /// Initiates a key event action (e.g., pressing Home or Back).
186    ///
187    /// Returns a `KeyeventBuilder` to configure and execute the action.
188    pub fn keyevent(&mut self, key_code: KeyCode) -> action::keyevent::KeyeventBuilder<'_> {
189        action::keyevent::KeyeventBuilder::new(self, key_code)
190    }
191}