Skip to main content

droidrun_core/driver/
recording.rs

1/// RecordingDriver — transparent proxy that logs device actions for macro replay.
2use std::collections::HashSet;
3use std::path::Path;
4use std::sync::Mutex;
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8
9use super::{Action, AppInfo, DeviceDriver};
10use crate::error::Result;
11
12/// A recorded action entry.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(tag = "action_type")]
15pub enum RecordedAction {
16    #[serde(rename = "tap")]
17    Tap { x: i32, y: i32 },
18
19    #[serde(rename = "swipe")]
20    Swipe {
21        start_x: i32,
22        start_y: i32,
23        end_x: i32,
24        end_y: i32,
25        duration_ms: u32,
26    },
27
28    #[serde(rename = "input_text")]
29    InputText { text: String, clear: bool },
30
31    #[serde(rename = "key_press")]
32    KeyPress { keycode: i32 },
33
34    #[serde(rename = "start_app")]
35    StartApp {
36        package: String,
37        activity: Option<String>,
38    },
39
40    #[serde(rename = "drag")]
41    Drag {
42        start_x: i32,
43        start_y: i32,
44        end_x: i32,
45        end_y: i32,
46        duration_ms: u32,
47    },
48}
49
50/// Proxy driver that records all mutating actions.
51///
52/// Read-only methods (screenshot, get_ui_tree, etc.) pass through
53/// without recording.
54pub struct RecordingDriver<D: DeviceDriver> {
55    inner: D,
56    log: Mutex<Vec<RecordedAction>>,
57}
58
59impl<D: DeviceDriver> RecordingDriver<D> {
60    pub fn new(inner: D) -> Self {
61        Self {
62            inner,
63            log: Mutex::new(Vec::new()),
64        }
65    }
66
67    /// Get all recorded actions.
68    pub fn recorded_actions(&self) -> Vec<RecordedAction> {
69        self.log.lock().unwrap().clone()
70    }
71
72    /// Clear the recording log.
73    pub fn clear_log(&self) {
74        self.log.lock().unwrap().clear();
75    }
76
77    /// Serialize the log to JSON.
78    pub fn to_json(&self) -> serde_json::Result<String> {
79        serde_json::to_string_pretty(&self.recorded_actions())
80    }
81
82    fn record(&self, action: RecordedAction) {
83        self.log.lock().unwrap().push(action);
84    }
85}
86
87#[async_trait]
88impl<D: DeviceDriver> DeviceDriver for RecordingDriver<D> {
89    async fn connect(&mut self) -> Result<()> {
90        self.inner.connect().await
91    }
92
93    async fn ensure_connected(&mut self) -> Result<()> {
94        self.inner.ensure_connected().await
95    }
96
97    async fn tap(&self, x: i32, y: i32) -> Result<()> {
98        self.inner.tap(x, y).await?;
99        self.record(RecordedAction::Tap { x, y });
100        Ok(())
101    }
102
103    async fn swipe(
104        &self,
105        x1: i32,
106        y1: i32,
107        x2: i32,
108        y2: i32,
109        duration_ms: u32,
110    ) -> Result<()> {
111        self.inner.swipe(x1, y1, x2, y2, duration_ms).await?;
112        self.record(RecordedAction::Swipe {
113            start_x: x1,
114            start_y: y1,
115            end_x: x2,
116            end_y: y2,
117            duration_ms,
118        });
119        Ok(())
120    }
121
122    async fn input_text(&self, text: &str, clear: bool) -> Result<bool> {
123        let result = self.inner.input_text(text, clear).await?;
124        self.record(RecordedAction::InputText {
125            text: text.to_string(),
126            clear,
127        });
128        Ok(result)
129    }
130
131    async fn press_key(&self, keycode: i32) -> Result<()> {
132        self.inner.press_key(keycode).await?;
133        self.record(RecordedAction::KeyPress { keycode });
134        Ok(())
135    }
136
137    async fn drag(
138        &self,
139        x1: i32,
140        y1: i32,
141        x2: i32,
142        y2: i32,
143        duration_ms: u32,
144    ) -> Result<()> {
145        self.inner.drag(x1, y1, x2, y2, duration_ms).await?;
146        self.record(RecordedAction::Drag {
147            start_x: x1,
148            start_y: y1,
149            end_x: x2,
150            end_y: y2,
151            duration_ms,
152        });
153        Ok(())
154    }
155
156    async fn start_app(&self, package: &str, activity: Option<&str>) -> Result<String> {
157        let result = self.inner.start_app(package, activity).await?;
158        self.record(RecordedAction::StartApp {
159            package: package.to_string(),
160            activity: activity.map(|a| a.to_string()),
161        });
162        Ok(result)
163    }
164
165    // ── Pass-through (not recorded) ────────────────────────────
166
167    async fn install_app(&self, path: &Path) -> Result<String> {
168        self.inner.install_app(path).await
169    }
170
171    async fn get_apps(&self, include_system: bool) -> Result<Vec<AppInfo>> {
172        self.inner.get_apps(include_system).await
173    }
174
175    async fn list_packages(&self, include_system: bool) -> Result<Vec<String>> {
176        self.inner.list_packages(include_system).await
177    }
178
179    async fn screenshot(&self, hide_overlay: bool) -> Result<Vec<u8>> {
180        self.inner.screenshot(hide_overlay).await
181    }
182
183    async fn get_ui_tree(&self) -> Result<serde_json::Value> {
184        self.inner.get_ui_tree().await
185    }
186
187    async fn get_date(&self) -> Result<String> {
188        self.inner.get_date().await
189    }
190
191    fn supported_actions(&self) -> &HashSet<Action> {
192        self.inner.supported_actions()
193    }
194}