Skip to main content

droidrun_core/driver/
android.rs

1/// AndroidDriver — ADB + Portal based device driver.
2use std::collections::HashSet;
3use std::path::Path;
4
5use async_trait::async_trait;
6use tracing::{debug, info};
7
8use droidrun_adb::AdbDevice;
9
10use crate::error::{DroidrunError, Result};
11use crate::portal::client::PortalClient;
12use crate::portal::keyboard;
13
14use super::{Action, AppInfo, DeviceDriver};
15
16const PORTAL_DEFAULT_TCP_PORT: u16 = 8080;
17
18/// Android device driver using ADB + DroidRun Portal.
19pub struct AndroidDriver {
20    serial: Option<String>,
21    use_tcp: bool,
22    remote_tcp_port: u16,
23    device: Option<AdbDevice>,
24    portal: Option<PortalClient>,
25    connected: bool,
26    supported: HashSet<Action>,
27}
28
29impl AndroidDriver {
30    /// Create a new Android driver.
31    pub fn new(serial: Option<&str>, use_tcp: bool) -> Self {
32        let supported = HashSet::from([
33            Action::Tap,
34            Action::Swipe,
35            Action::InputText,
36            Action::PressKey,
37            Action::StartApp,
38            Action::InstallApp,
39            Action::Screenshot,
40            Action::GetUiTree,
41            Action::GetDate,
42            Action::GetApps,
43            Action::ListPackages,
44            Action::Drag,
45        ]);
46
47        Self {
48            serial: serial.map(|s| s.to_string()),
49            use_tcp,
50            remote_tcp_port: PORTAL_DEFAULT_TCP_PORT,
51            device: None,
52            portal: None,
53            connected: false,
54            supported,
55        }
56    }
57
58    /// Get a reference to the underlying ADB device.
59    pub fn adb_device(&self) -> Result<&AdbDevice> {
60        self.device.as_ref().ok_or(DroidrunError::NotConnected)
61    }
62
63    /// Get a reference to the Portal client.
64    pub fn portal_client(&self) -> Result<&PortalClient> {
65        self.portal.as_ref().ok_or(DroidrunError::NotConnected)
66    }
67}
68
69#[async_trait]
70impl DeviceDriver for AndroidDriver {
71    // ── Lifecycle ──────────────────────────────────────────────
72
73    async fn connect(&mut self) -> Result<()> {
74        if self.connected {
75            return Ok(());
76        }
77
78        // Resolve device
79        let server = droidrun_adb::AdbServer::default();
80        let device = server.resolve_device(self.serial.as_deref()).await?;
81
82        // Verify device is online
83        let state = device.get_state().await?;
84        if !state.is_online() {
85            return Err(DroidrunError::Adb(droidrun_adb::AdbError::DeviceNotOnline(
86                state.to_string(),
87            )));
88        }
89
90        info!("connected to device: {}", device.serial);
91
92        // Create Portal client
93        let mut portal = PortalClient::new(device.clone(), self.use_tcp, self.remote_tcp_port);
94        portal.connect().await?;
95
96        // Setup keyboard
97        keyboard::setup_keyboard(&device).await?;
98
99        self.device = Some(device);
100        self.portal = Some(portal);
101        self.connected = true;
102
103        Ok(())
104    }
105
106    async fn ensure_connected(&mut self) -> Result<()> {
107        if !self.connected {
108            self.connect().await?;
109        }
110        Ok(())
111    }
112
113    // ── Input actions ──────────────────────────────────────────
114
115    async fn tap(&self, x: i32, y: i32) -> Result<()> {
116        let device = self.adb_device()?;
117        device.tap(x, y).await?;
118        Ok(())
119    }
120
121    async fn swipe(
122        &self,
123        x1: i32,
124        y1: i32,
125        x2: i32,
126        y2: i32,
127        duration_ms: u32,
128    ) -> Result<()> {
129        let device = self.adb_device()?;
130        device.swipe(x1, y1, x2, y2, duration_ms).await?;
131        tokio::time::sleep(std::time::Duration::from_millis(duration_ms as u64)).await;
132        Ok(())
133    }
134
135    async fn input_text(&self, text: &str, clear: bool) -> Result<bool> {
136        let portal = self.portal_client()?;
137        portal.input_text(text, clear).await
138    }
139
140    async fn press_key(&self, keycode: i32) -> Result<()> {
141        let device = self.adb_device()?;
142        device.keyevent(keycode).await?;
143        Ok(())
144    }
145
146    async fn drag(
147        &self,
148        x1: i32,
149        y1: i32,
150        x2: i32,
151        y2: i32,
152        duration_ms: u32,
153    ) -> Result<()> {
154        let device = self.adb_device()?;
155        device.drag(x1, y1, x2, y2, duration_ms).await?;
156        Ok(())
157    }
158
159    // ── App management ─────────────────────────────────────────
160
161    async fn start_app(&self, package: &str, activity: Option<&str>) -> Result<String> {
162        let device = self.adb_device()?;
163        debug!("starting app {package} with activity {activity:?}");
164        match device.app_start(package, activity).await {
165            Ok(result) => Ok(result),
166            Err(e) => Ok(format!("Failed to start app {package}: {e}")),
167        }
168    }
169
170    async fn install_app(&self, path: &Path) -> Result<String> {
171        let device = self.adb_device()?;
172        let result = device.install(path, &["-g"]).await?;
173        Ok(result)
174    }
175
176    async fn get_apps(&self, include_system: bool) -> Result<Vec<AppInfo>> {
177        let portal = self.portal_client()?;
178        portal.get_apps(include_system).await
179    }
180
181    async fn list_packages(&self, include_system: bool) -> Result<Vec<String>> {
182        let device = self.adb_device()?;
183        let flags = if include_system {
184            vec![]
185        } else {
186            vec!["-3"]
187        };
188        let pkgs = device.list_packages(&flags).await?;
189        Ok(pkgs)
190    }
191
192    // ── State / observation ────────────────────────────────────
193
194    async fn screenshot(&self, hide_overlay: bool) -> Result<Vec<u8>> {
195        let portal = self.portal_client()?;
196        portal.take_screenshot(hide_overlay).await
197    }
198
199    async fn get_ui_tree(&self) -> Result<serde_json::Value> {
200        let portal = self.portal_client()?;
201        portal.get_state().await
202    }
203
204    async fn get_date(&self) -> Result<String> {
205        let device = self.adb_device()?;
206        device.get_date().await.map_err(|e| e.into())
207    }
208
209    // ── Capabilities ───────────────────────────────────────────
210
211    fn supported_actions(&self) -> &HashSet<Action> {
212        &self.supported
213    }
214}