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}