Skip to main content

android/session/
input.rs

1//! The input sink: points in device points to the agent's input and text
2//! requests in physical pixels, or to the controller's touches.
3
4use std::sync::atomic::Ordering;
5use std::sync::{Arc, PoisonError};
6use std::time::{Duration, Instant};
7
8use mirror::Point;
9use mirror::backend::{BackendError, Input, InputAck, InputSink, Release};
10use protocol::code;
11use protocol::message::{Call, KeyParams, Reply, SwipeParams, TapParams, TextMethod, TextParams};
12
13use super::Inner;
14use crate::emulator::{self, TouchPoint};
15
16/// How often a swipe on the controller moves its contact, as the agent's
17/// swipe does.
18const SWIPE_STEP: Duration = Duration::from_millis(8);
19
20/// How long the pointer of a swipe released resting rests at the end of
21/// its path before it moves one pixel on and lifts (task 10.16). Android's
22/// velocity tracker takes a pointer that has not moved for 40 ms
23/// (`ASSUME_POINTER_STOPPED_TIME`) as stopped when its next event comes; 20
24/// ms more leave room for the events' timing. On the API 36 emulator a
25/// 600-point swipe over 300 ms released this way moved the Expo fixture's
26/// list 555 to 584 points and the Compose fixture's 592, where one that
27/// lifted moving moved them 1,103 to 1,319.
28pub const REST: Duration = Duration::from_millis(60);
29
30fn millis(d: Duration) -> u32 {
31    u32::try_from(d.as_millis()).unwrap_or(u32::MAX)
32}
33
34/// How long the pointer of a swipe released as `release` rests before it
35/// lifts.
36fn rest(release: Release) -> Option<Duration> {
37    match release {
38        Release::Moving => None,
39        Release::Resting => Some(REST),
40    }
41}
42
43/// The agent request for `input`, at `scale` physical pixels per point.
44pub fn agent_call(input: &Input, scale: f64) -> Call {
45    match input {
46        Input::Tap { at, hold } => {
47            let (x, y) = at.to_pixels(scale);
48            Call::Tap(TapParams {
49                x,
50                y,
51                duration_ms: hold.map(millis),
52            })
53        }
54        Input::Swipe {
55            from,
56            to,
57            duration,
58            release,
59        } => {
60            let (x1, y1) = from.to_pixels(scale);
61            let (x2, y2) = to.to_pixels(scale);
62            Call::Swipe(SwipeParams {
63                x1,
64                y1,
65                x2,
66                y2,
67                duration_ms: millis(*duration),
68                rest_ms: rest(*release).map(millis),
69            })
70        }
71        Input::Key { code, meta } => Call::Key(KeyParams {
72            code: *code,
73            meta: *meta,
74        }),
75        Input::Text { text, node, method } => Call::TextSet(TextParams {
76            text: text.clone(),
77            node: node.map(|handle| handle.node),
78            mode: (*method != TextMethod::AUTO).then_some(*method),
79        }),
80    }
81}
82
83/// How long the agent takes to perform `input` before it answers.
84fn gesture_time(input: &Input) -> Duration {
85    match input {
86        Input::Tap { hold, .. } => hold.unwrap_or(Duration::ZERO),
87        Input::Swipe {
88            duration, release, ..
89        } => *duration + rest(*release).unwrap_or_default(),
90        Input::Key { .. } | Input::Text { .. } => Duration::ZERO,
91    }
92}
93
94/// The acknowledgement an agent reply carries.
95pub fn input_ack(reply: Reply) -> Result<InputAck, protocol::Error> {
96    match reply {
97        Reply::Input(ack) => Ok(InputAck {
98            accepted: ack.accepted,
99            method: None,
100        }),
101        Reply::Text(ack) => Ok(InputAck {
102            accepted: true,
103            method: Some(ack.method),
104        }),
105        _ => Err(protocol::Error::new(
106            code::PROTOCOL_ERROR,
107            "the agent answered an input request with another result",
108        )),
109    }
110}
111
112/// The controller touches of a swipe from `from` to `to` over `duration`,
113/// each with its offset from the start: contact 0 down at the start, a move
114/// every `step` along the line, and up at the end, as the agent's swipe
115/// does. With a `rest`, the contact rests at the end that long, then moves
116/// one pixel on along the line and goes up there, as the agent's swipe does
117/// with `rest_ms`: a touch that repeats the last position changes nothing
118/// the platform sees.
119pub fn swipe_touches(
120    from: (i32, i32),
121    to: (i32, i32),
122    duration: Duration,
123    step: Duration,
124    rest: Option<Duration>,
125) -> Vec<(Duration, TouchPoint)> {
126    let steps = if step.is_zero() {
127        1
128    } else {
129        duration.as_nanos().div_ceil(step.as_nanos()).max(1)
130    };
131    let steps = u32::try_from(steps).unwrap_or(u32::MAX);
132    let along = |from: i32, to: i32, i: u32| {
133        from + ((f64::from(to - from) * f64::from(i)) / f64::from(steps)).round() as i32
134    };
135    let mut touches = Vec::with_capacity(steps as usize + 3);
136    touches.push((Duration::ZERO, TouchPoint::down(from.0, from.1)));
137    for i in 1..=steps {
138        touches.push((
139            duration * i / steps,
140            TouchPoint::down(along(from.0, to.0, i), along(from.1, to.1, i)),
141        ));
142    }
143    let (at, end) = match rest {
144        Some(rest) => {
145            let on = (
146                to.0 + (to.0 - from.0).signum(),
147                to.1 + (to.1 - from.1).signum(),
148            );
149            touches.push((duration + rest, TouchPoint::down(on.0, on.1)));
150            (duration + rest, on)
151        }
152        None => (duration, to),
153    };
154    touches.push((at, TouchPoint::up(end.0, end.1)));
155    touches
156}
157
158/// The session's input sink.
159pub(crate) struct SessionInput {
160    pub(crate) inner: Arc<Inner>,
161}
162
163/// Whether a controller failure happened before anything reached the
164/// device, so that the agent can take the same input.
165enum ControllerFailure {
166    BeforeContact(emulator::Error),
167    AfterContact(emulator::Error),
168}
169
170impl SessionInput {
171    /// Taps and swipes through the controller; `None` for input it does not
172    /// take.
173    fn through_controller(&self, input: &Input) -> Option<Result<InputAck, ControllerFailure>> {
174        let inner = &*self.inner;
175        let link = inner.controller.as_ref()?;
176        if inner
177            .controller_failed
178            .lock()
179            .unwrap_or_else(PoisonError::into_inner)
180            .is_some()
181        {
182            return None;
183        }
184        let pixels = |p: &Point| p.to_pixels(inner.scale);
185        let touches = match input {
186            Input::Tap { at, hold } => {
187                let (x, y) = pixels(at);
188                let hold = hold.unwrap_or(Duration::ZERO);
189                vec![
190                    (Duration::ZERO, TouchPoint::down(x, y)),
191                    (hold, TouchPoint::up(x, y)),
192                ]
193            }
194            Input::Swipe {
195                from,
196                to,
197                duration,
198                release,
199            } => swipe_touches(
200                pixels(from),
201                pixels(to),
202                *duration,
203                SWIPE_STEP,
204                rest(*release),
205            ),
206            Input::Key { .. } | Input::Text { .. } => return None,
207        };
208        let controller = link.controller().clone();
209        let result = link.block_on(async move {
210            let start = tokio::time::Instant::now();
211            for (i, (at, point)) in touches.iter().enumerate() {
212                tokio::time::sleep_until(start + *at).await;
213                if let Err(error) = controller.touch(std::slice::from_ref(point)).await {
214                    return Err(if i == 0 {
215                        ControllerFailure::BeforeContact(error)
216                    } else {
217                        ControllerFailure::AfterContact(error)
218                    });
219                }
220            }
221            Ok(())
222        });
223        if !matches!(result, Err(ControllerFailure::BeforeContact(_))) {
224            inner.note_foreign_input(Instant::now());
225            inner.controller_inputs.fetch_add(1, Ordering::SeqCst);
226        }
227        Some(result.map(|()| InputAck {
228            accepted: true,
229            method: None,
230        }))
231    }
232}
233
234impl InputSink for SessionInput {
235    fn perform(&mut self, input: &Input) -> Result<InputAck, BackendError> {
236        match self.through_controller(input) {
237            Some(Ok(ack)) => return Ok(ack),
238            Some(Err(failure)) => {
239                let (error, retry) = match failure {
240                    ControllerFailure::BeforeContact(error) => (error, true),
241                    ControllerFailure::AfterContact(error) => (error, false),
242                };
243                *self
244                    .inner
245                    .controller_failed
246                    .lock()
247                    .unwrap_or_else(PoisonError::into_inner) = Some(error.to_string());
248                if !retry {
249                    return Err(BackendError::new(
250                        code::INTERNAL,
251                        format!(
252                            "the emulator controller failed during the gesture ({error}); \
253                             later input goes through the agent"
254                        ),
255                    ));
256                }
257            }
258            None => {}
259        }
260        let inner = &*self.inner;
261        let reply = inner.connection.call(
262            agent_call(input, inner.scale),
263            inner.options.input_deadline + gesture_time(input),
264        )?;
265        inner.agent_inputs.fetch_add(1, Ordering::SeqCst);
266        input_ack(reply)
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use mirror::Point;
274    use mirror::backend::NodeHandle;
275    use protocol::code;
276    use protocol::message::{
277        InputAck as AgentAck, KeyParams, SwipeParams, TapParams, TextAck, TextMethod, TextParams,
278    };
279
280    const SCALE: f64 = 2.625;
281
282    #[test]
283    fn taps_and_long_presses_go_out_in_pixels() {
284        let tap = Input::Tap {
285            at: Point::new(100.0, 200.0),
286            hold: None,
287        };
288        assert_eq!(
289            agent_call(&tap, SCALE),
290            Call::Tap(TapParams {
291                x: 263,
292                y: 525,
293                duration_ms: None
294            })
295        );
296        let press = Input::Tap {
297            at: Point::new(10.4, 0.2),
298            hold: Some(Duration::from_millis(800)),
299        };
300        assert_eq!(
301            agent_call(&press, SCALE),
302            Call::Tap(TapParams {
303                x: 27,
304                y: 1,
305                duration_ms: Some(800)
306            })
307        );
308    }
309
310    #[test]
311    fn swipes_keep_their_duration_and_keys_their_code() {
312        let swipe = Input::Swipe {
313            from: Point::new(200.0, 800.0),
314            to: Point::new(200.0, 200.0),
315            duration: Duration::from_millis(300),
316            release: Release::Moving,
317        };
318        assert_eq!(
319            agent_call(&swipe, SCALE),
320            Call::Swipe(SwipeParams {
321                x1: 525,
322                y1: 2100,
323                x2: 525,
324                y2: 525,
325                duration_ms: 300,
326                rest_ms: None
327            })
328        );
329        let key = Input::Key {
330            code: 66,
331            meta: Some(1),
332        };
333        assert_eq!(
334            agent_call(&key, SCALE),
335            Call::Key(KeyParams {
336                code: 66,
337                meta: Some(1)
338            })
339        );
340    }
341
342    #[test]
343    fn text_names_the_node_of_the_latest_read_and_its_method() {
344        let text = Input::Text {
345            text: "hunter2".into(),
346            node: Some(NodeHandle {
347                window: 3,
348                node: 42,
349            }),
350            method: TextMethod::DIRECT,
351        };
352        assert_eq!(
353            agent_call(&text, SCALE),
354            Call::TextSet(TextParams {
355                text: "hunter2".into(),
356                node: Some(42),
357                mode: Some(TextMethod::DIRECT)
358            })
359        );
360        let focused = Input::Text {
361            text: "a".into(),
362            node: None,
363            method: TextMethod::AUTO,
364        };
365        assert_eq!(
366            agent_call(&focused, SCALE),
367            Call::TextSet(TextParams {
368                text: "a".into(),
369                node: None,
370                mode: None
371            }),
372            "auto leaves the choice to the agent"
373        );
374    }
375
376    #[test]
377    fn acknowledgements_carry_the_platforms_verdict_and_the_text_method() {
378        assert_eq!(
379            input_ack(Reply::Input(AgentAck { accepted: false })).unwrap(),
380            InputAck {
381                accepted: false,
382                method: None
383            }
384        );
385        assert_eq!(
386            input_ack(Reply::Text(TextAck {
387                method: TextMethod::KEYS
388            }))
389            .unwrap(),
390            InputAck {
391                accepted: true,
392                method: Some(TextMethod::KEYS)
393            }
394        );
395        let error = input_ack(Reply::Empty).unwrap_err();
396        assert_eq!(error.code, code::PROTOCOL_ERROR);
397    }
398
399    /// Task 10.16: a swipe released resting asks the agent to rest its
400    /// pointer 60 ms at the end before it moves one pixel on and lifts, and
401    /// waits that much longer for the agent's answer.
402    #[test]
403    fn a_swipe_released_resting_asks_the_agent_to_rest_60_ms() {
404        let swipe = Input::Swipe {
405            from: Point::new(200.0, 800.0),
406            to: Point::new(200.0, 200.0),
407            duration: Duration::from_millis(300),
408            release: Release::Resting,
409        };
410        assert_eq!(
411            agent_call(&swipe, SCALE),
412            Call::Swipe(SwipeParams {
413                x1: 525,
414                y1: 2100,
415                x2: 525,
416                y2: 525,
417                duration_ms: 300,
418                rest_ms: Some(60)
419            })
420        );
421        assert_eq!(gesture_time(&swipe), Duration::from_millis(360));
422    }
423
424    /// Task 10.16: on the controller, a swipe released resting reaches its
425    /// end at its duration, rests, then moves one pixel on along its line
426    /// and lifts there. Touches that repeat the last position change
427    /// nothing the platform sees, so the move must go somewhere: the
428    /// velocity tracker takes a pointer that has not moved for 40 ms as
429    /// stopped when its next event comes, and a View-based list, such as the
430    /// Expo fixture's, takes its velocity from the moves before the up.
431    #[test]
432    fn a_swipe_released_resting_on_the_controller_rests_then_moves_a_pixel_on_and_lifts() {
433        let touches = swipe_touches(
434            (100, 1000),
435            (100, 200),
436            Duration::from_millis(40),
437            Duration::from_millis(8),
438            Some(Duration::from_millis(60)),
439        );
440        let n = touches.len();
441        assert_eq!(n, 8, "{touches:?}");
442        assert_eq!(
443            touches[n - 3],
444            (Duration::from_millis(40), TouchPoint::down(100, 200))
445        );
446        assert_eq!(
447            touches[n - 2],
448            (Duration::from_millis(100), TouchPoint::down(100, 199))
449        );
450        assert_eq!(
451            touches[n - 1],
452            (Duration::from_millis(100), TouchPoint::up(100, 199))
453        );
454        let across = swipe_touches(
455            (1000, 500),
456            (200, 500),
457            Duration::from_millis(40),
458            Duration::from_millis(8),
459            Some(Duration::from_millis(60)),
460        );
461        assert_eq!(
462            across.last(),
463            Some(&(Duration::from_millis(100), TouchPoint::up(199, 500)))
464        );
465    }
466
467    #[test]
468    fn a_swipe_on_the_controller_is_down_moves_and_up_along_the_line() {
469        let touches = swipe_touches(
470            (100, 1000),
471            (100, 200),
472            Duration::from_millis(40),
473            Duration::from_millis(8),
474            None,
475        );
476        let first = touches.first().unwrap();
477        assert_eq!(first, &(Duration::ZERO, TouchPoint::down(100, 1000)));
478        let last = touches.last().unwrap();
479        assert_eq!(last, &(Duration::from_millis(40), TouchPoint::up(100, 200)));
480        let moves = &touches[1..touches.len() - 1];
481        assert_eq!(moves.len(), 5, "{touches:?}");
482        assert_eq!(moves.last().unwrap().1, TouchPoint::down(100, 200));
483        let mut previous = (Duration::ZERO, 1000);
484        for (at, point) in moves {
485            assert!(*at > previous.0 && point.y < previous.1, "{touches:?}");
486            assert_eq!(point.pressure, crate::emulator::DOWN_PRESSURE);
487            previous = (*at, point.y);
488        }
489    }
490
491    #[test]
492    fn a_swipe_without_duration_still_moves_once() {
493        let touches = swipe_touches(
494            (0, 0),
495            (50, 0),
496            Duration::ZERO,
497            Duration::from_millis(8),
498            None,
499        );
500        assert_eq!(
501            touches,
502            vec![
503                (Duration::ZERO, TouchPoint::down(0, 0)),
504                (Duration::ZERO, TouchPoint::down(50, 0)),
505                (Duration::ZERO, TouchPoint::up(50, 0)),
506            ]
507        );
508    }
509}