Skip to main content

starnix_modules_input_event_conversion/
touch_fuchsia_to_linux.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl_fuchsia_ui_pointer::{
6    EventPhase as FidlEventPhase, TouchEvent as FidlTouchEvent, TouchPointerSample,
7};
8use sorted_vec_map::SortedVecMap;
9use starnix_logging::log_warn;
10use starnix_types::time::timeval_from_time;
11use starnix_uapi::uapi;
12use std::collections::VecDeque;
13
14type SlotId = usize;
15type TrackingId = u32;
16type TimeNanos = i64;
17
18/// TRACKING_ID changed to -1 means the contact is lifted.
19const LIFTED_TRACKING_ID: i32 = -1;
20
21#[derive(Debug, thiserror::Error)]
22enum TouchEventConversionError {
23    #[error("Event does not include enough information")]
24    NotEnoughInformation,
25    #[error("no more available slot id")]
26    NoMoreAvailableSlotId,
27    #[error("receive pointer add already added")]
28    PointerAdded,
29    #[error("receive pointer change/remove before added")]
30    PointerNotFound,
31    #[error("Input pipeline does not send out Cancel")]
32    PointerCancel,
33}
34
35#[derive(Debug, Clone, PartialEq)]
36struct TouchEvent {
37    time_nanos: TimeNanos,
38    pointer_id: TrackingId,
39    phase: FidlEventPhase,
40    x: i32,
41    y: i32,
42}
43
44impl TryFrom<FidlTouchEvent> for TouchEvent {
45    type Error = TouchEventConversionError;
46    fn try_from(e: FidlTouchEvent) -> Result<TouchEvent, Self::Error> {
47        match e {
48            FidlTouchEvent {
49                timestamp: Some(time_nanos),
50                pointer_sample:
51                    Some(TouchPointerSample {
52                        position_in_viewport: Some([x, y]),
53                        phase: Some(phase),
54                        interaction: Some(id),
55                        ..
56                    }),
57                ..
58            } => Ok(TouchEvent {
59                time_nanos,
60                pointer_id: id.pointer_id,
61                phase,
62                x: x as i32,
63                y: y as i32,
64            }),
65            _ => Err(TouchEventConversionError::NotEnoughInformation),
66        }
67    }
68}
69
70/// FuchsiaTouchEventToLinuxTouchEventConverter handles fuchsia.ui.pointer.TouchEvents
71/// and converts them to Linux uapi::input_event in Multi Touch Protocol B.
72#[derive(Debug, Default, PartialEq)]
73pub struct FuchsiaTouchEventToLinuxTouchEventConverter {
74    pointer_id_to_slot_id: SortedVecMap<TrackingId, SlotId>,
75    pointer_id_to_event: SortedVecMap<TrackingId, TouchEvent>,
76}
77
78const MAX_TOUCH_CONTACT: usize = 10;
79
80pub struct LinuxTouchEventBatch {
81    // Linux Multi Touch Protocol B events
82    pub events: VecDeque<uapi::input_event>,
83    pub last_event_time_ns: i64,
84    pub count_converted_fidl_events: u64,
85    pub count_ignored_fidl_events: u64,
86    pub count_unexpected_fidl_events: u64,
87}
88
89impl LinuxTouchEventBatch {
90    pub fn new() -> Self {
91        Self {
92            events: VecDeque::new(),
93            last_event_time_ns: 0,
94            count_converted_fidl_events: 0,
95            count_ignored_fidl_events: 0,
96            count_unexpected_fidl_events: 0,
97        }
98    }
99}
100
101impl FuchsiaTouchEventToLinuxTouchEventConverter {
102    pub fn create() -> Self {
103        Self {
104            pointer_id_to_slot_id: SortedVecMap::new(),
105            pointer_id_to_event: SortedVecMap::new(),
106        }
107    }
108
109    /// In Protocol B, the driver should only advertise as many slots as the hardware can report
110    /// so this converter uses `available_slot_id` to find the first available slot id.
111    fn available_slot_id(&self) -> Option<SlotId> {
112        let mut used_slot_ids = bit_vec::BitVec::<u32>::from_elem(MAX_TOUCH_CONTACT, false);
113        for slot_id in self.pointer_id_to_slot_id.values() {
114            used_slot_ids.set(*slot_id, true);
115        }
116
117        used_slot_ids.iter().position(|used| !used)
118    }
119
120    /// Converts fidl touch events to a batch of Linux Multi Touch Protocol B events.
121    ///
122    /// One vector of fidl touch events may convert to multiple Linux Multi Touch Protocol B
123    /// sequences because:
124    /// - Same pointer happens multiple times in the vector of fidl touch events.
125    /// - Linux Multi Touch Protocol B does not allow slot with same id appear multiple times
126    ///   one sequence.
127    pub fn handle(&mut self, events: Vec<FidlTouchEvent>) -> LinuxTouchEventBatch {
128        let mut batch = LinuxTouchEventBatch::new();
129
130        // TODO(https://fxbug.dev/348726475): Group events by timestamp here because events from
131        // fuchsia.ui.pointer.touch.Watch may not sorted by timestamp.
132        let mut sequences: SortedVecMap<TimeNanos, Vec<TouchEvent>> = SortedVecMap::new();
133        for event in events.into_iter() {
134            match TouchEvent::try_from(event) {
135                Ok(e) => {
136                    if let Some(vec) = sequences.get_mut(&e.time_nanos) {
137                        vec.push(e);
138                    } else {
139                        sequences.insert(e.time_nanos, vec![e]);
140                    }
141                }
142                Err(_) => {
143                    batch.count_ignored_fidl_events += 1;
144                }
145            }
146        }
147
148        if sequences.is_empty() {
149            return batch;
150        }
151
152        batch.last_event_time_ns = *sequences.iter().next_back().unwrap().0;
153
154        for (time_nanos, seq) in sequences.iter() {
155            let count_events = seq.len() as u64;
156            match self.translate_sequence(*time_nanos, seq) {
157                Ok(mut res) => {
158                    batch.events.append(&mut res);
159                    batch.count_converted_fidl_events += count_events;
160                }
161                Err(e) => {
162                    batch.count_unexpected_fidl_events += count_events;
163                    self.reset_state();
164                    log_warn!("{}", e);
165                }
166            }
167        }
168
169        batch
170    }
171
172    /// Translates a vec of fidl FidlTouchEvent to Linux Multi Touch Protocol B sequence. Caller
173    /// ensures the given vec does not include duplicated pointer, and all event includes same
174    /// timestamp.
175    ///
176    /// Return err for unexpected events which should be filtered in earlier component:
177    /// input-pipeline and scenic. If 1 event is unexpected, translate_sequence() drops all events
178    /// from the same scan from driver.
179    fn translate_sequence(
180        &mut self,
181        time_nanos: TimeNanos,
182        events: &Vec<TouchEvent>,
183    ) -> Result<VecDeque<uapi::input_event>, TouchEventConversionError> {
184        let mut existing_slot: VecDeque<uapi::input_event> = VecDeque::new();
185        let mut new_slots: VecDeque<uapi::input_event> = VecDeque::new();
186
187        let time = timeval_from_time(zx::MonotonicInstant::from_nanos(time_nanos));
188
189        let no_contact_before_process_events = self.pointer_id_to_slot_id.is_empty();
190        let mut need_btn_touch_down = false;
191        let mut need_btn_touch_up = false;
192
193        // TODO(https://fxbug.dev/314151713): use event.device_info to route event to different
194        // device file.
195
196        for (index, event) in events.iter().enumerate() {
197            let pointer_id = event.pointer_id;
198            let slot_id = self.pointer_id_to_slot_id.get(&pointer_id).copied();
199            let previous_event = self.pointer_id_to_event.insert(pointer_id, event.clone());
200
201            match event.phase {
202                FidlEventPhase::Add => match (slot_id, previous_event) {
203                    (None, None) => {
204                        let new_slot_id = match self.available_slot_id() {
205                            Some(index) => index,
206                            None => {
207                                return Err(TouchEventConversionError::NoMoreAvailableSlotId);
208                            }
209                        };
210
211                        if no_contact_before_process_events {
212                            need_btn_touch_down = true;
213                        }
214
215                        self.pointer_id_to_slot_id.insert(pointer_id, new_slot_id);
216
217                        new_slots.push_back(uapi::input_event {
218                            time,
219                            type_: uapi::EV_ABS as u16,
220                            code: uapi::ABS_MT_SLOT as u16,
221                            value: new_slot_id as i32,
222                        });
223
224                        new_slots.push_back(uapi::input_event {
225                            time,
226                            type_: uapi::EV_ABS as u16,
227                            code: uapi::ABS_MT_TRACKING_ID as u16,
228                            value: pointer_id as i32,
229                        });
230
231                        new_slots.push_back(uapi::input_event {
232                            time,
233                            type_: uapi::EV_ABS as u16,
234                            code: uapi::ABS_MT_POSITION_X as u16,
235                            value: event.x,
236                        });
237
238                        new_slots.push_back(uapi::input_event {
239                            time,
240                            type_: uapi::EV_ABS as u16,
241                            code: uapi::ABS_MT_POSITION_Y as u16,
242                            value: event.y,
243                        });
244                    }
245                    (_, _) => {
246                        return Err(TouchEventConversionError::PointerAdded);
247                    }
248                },
249                FidlEventPhase::Change => match (slot_id, previous_event) {
250                    (Some(slot_id), Some(prev)) => {
251                        if prev.x != event.x || prev.y != event.y {
252                            existing_slot.push_back(uapi::input_event {
253                                time,
254                                type_: uapi::EV_ABS as u16,
255                                code: uapi::ABS_MT_SLOT as u16,
256                                value: slot_id as i32,
257                            });
258                        }
259
260                        if prev.x != event.x {
261                            existing_slot.push_back(uapi::input_event {
262                                time,
263                                type_: uapi::EV_ABS as u16,
264                                code: uapi::ABS_MT_POSITION_X as u16,
265                                value: event.x,
266                            });
267                        }
268
269                        if prev.y != event.y {
270                            existing_slot.push_back(uapi::input_event {
271                                time,
272                                type_: uapi::EV_ABS as u16,
273                                code: uapi::ABS_MT_POSITION_Y as u16,
274                                value: event.y,
275                            });
276                        }
277                    }
278                    (_, _) => {
279                        return Err(TouchEventConversionError::PointerNotFound);
280                    }
281                },
282                FidlEventPhase::Remove => match (slot_id, previous_event) {
283                    (Some(slot_id), Some(_)) => {
284                        self.pointer_id_to_slot_id.remove(&pointer_id);
285                        self.pointer_id_to_event.remove(&pointer_id);
286
287                        // Ensure BTN_TOUCH up event is only sent when the last pointer is lifted.
288                        // Here check if the event is the last event from the vec prevents a false
289                        // BTN_TOUCH up event if any error reset_state of this converter.
290                        if index == events.len() - 1 && self.pointer_id_to_slot_id.is_empty() {
291                            need_btn_touch_up = true;
292                        }
293
294                        existing_slot.push_back(uapi::input_event {
295                            time,
296                            type_: uapi::EV_ABS as u16,
297                            code: uapi::ABS_MT_SLOT as u16,
298                            value: slot_id as i32,
299                        });
300
301                        existing_slot.push_back(uapi::input_event {
302                            time,
303                            type_: uapi::EV_ABS as u16,
304                            code: uapi::ABS_MT_TRACKING_ID as u16,
305                            value: LIFTED_TRACKING_ID,
306                        });
307                    }
308                    (_, _) => {
309                        return Err(TouchEventConversionError::PointerNotFound);
310                    }
311                },
312                FidlEventPhase::Cancel => {
313                    return Err(TouchEventConversionError::PointerCancel);
314                }
315            }
316        }
317
318        let mut result: VecDeque<uapi::input_event> = VecDeque::new();
319
320        result.append(&mut existing_slot);
321        result.append(&mut new_slots);
322
323        if need_btn_touch_down {
324            result.push_back(uapi::input_event {
325                time,
326                type_: uapi::EV_KEY as u16,
327                code: uapi::BTN_TOUCH as u16,
328                value: 1,
329            });
330        } else if need_btn_touch_up {
331            result.push_back(uapi::input_event {
332                time,
333                type_: uapi::EV_KEY as u16,
334                code: uapi::BTN_TOUCH as u16,
335                value: 0,
336            });
337        }
338
339        if result.len() > 0 {
340            result.push_back(uapi::input_event {
341                time,
342                type_: uapi::EV_SYN as u16,
343                code: uapi::SYN_REPORT as u16,
344                value: 0,
345            });
346        }
347
348        Ok(result)
349    }
350
351    fn reset_state(&mut self) {
352        self.pointer_id_to_slot_id = SortedVecMap::new();
353        self.pointer_id_to_event = SortedVecMap::new();
354    }
355}
356
357#[cfg(test)]
358mod touchscreen_fuchsia_linux_tests {
359    use super::*;
360    use fidl_fuchsia_ui_pointer::TouchInteractionId;
361    use pretty_assertions::assert_eq;
362    use test_case::test_case;
363
364    fn make_touch_event_with_coords_phase_id_time(
365        x: f32,
366        y: f32,
367        phase: FidlEventPhase,
368        pointer_id: u32,
369        time_nanos: i64,
370    ) -> FidlTouchEvent {
371        FidlTouchEvent {
372            timestamp: Some(time_nanos),
373            pointer_sample: Some(TouchPointerSample {
374                position_in_viewport: Some([x, y]),
375                phase: Some(phase),
376                interaction: Some(TouchInteractionId {
377                    pointer_id,
378                    device_id: 0,
379                    interaction_id: 0,
380                }),
381                ..Default::default()
382            }),
383            ..Default::default()
384        }
385    }
386
387    fn make_touch_event_with_coords_phase_id(
388        x: f32,
389        y: f32,
390        phase: FidlEventPhase,
391        pointer_id: u32,
392    ) -> FidlTouchEvent {
393        make_touch_event_with_coords_phase_id_time(x, y, phase, pointer_id, 0)
394    }
395
396    fn make_uapi_input_event_with_time(
397        ty: u32,
398        code: u32,
399        value: i32,
400        time_nanos: i64,
401    ) -> uapi::input_event {
402        uapi::input_event {
403            time: timeval_from_time(zx::MonotonicInstant::from_nanos(time_nanos)),
404            type_: ty as u16,
405            code: code as u16,
406            value,
407        }
408    }
409
410    fn make_uapi_input_event(ty: u32, code: u32, value: i32) -> uapi::input_event {
411        make_uapi_input_event_with_time(ty, code, value, 0)
412    }
413
414    fn make_internal_touch_event(
415        time_nanos: i64,
416        x: i32,
417        y: i32,
418        phase: FidlEventPhase,
419        pointer_id: u32,
420    ) -> TouchEvent {
421        TouchEvent { time_nanos, pointer_id, phase, x, y }
422    }
423
424    #[test_case(FidlTouchEvent::default(); "not enough fields")]
425    fn ignored_events(e: FidlTouchEvent) {
426        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
427        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
428            10.0,
429            20.0,
430            FidlEventPhase::Add,
431            1,
432        )]);
433        let batch = converter.handle(vec![e]);
434        assert_eq!(batch.events, vec![]);
435        assert_eq!(batch.last_event_time_ns, 0);
436        assert_eq!(batch.count_converted_fidl_events, 0);
437        assert_eq!(batch.count_ignored_fidl_events, 1);
438        assert_eq!(batch.count_unexpected_fidl_events, 0);
439    }
440
441    #[test_case(make_touch_event_with_coords_phase_id(
442        1.0,
443        2.0,
444        FidlEventPhase::Add,
445        1,
446    ); "touch add pointer already added")]
447    #[test_case(make_touch_event_with_coords_phase_id(
448        1.0,
449        2.0,
450        FidlEventPhase::Change,
451        2,
452    ); "touch change pointer not added")]
453    #[test_case(make_touch_event_with_coords_phase_id(
454        0.0,
455        0.0,
456        FidlEventPhase::Remove,
457        2,
458    ); "touch remove pointer not added")]
459    #[test_case(make_touch_event_with_coords_phase_id(
460        0.0,
461        0.0,
462        FidlEventPhase::Cancel,
463        1,
464    ); "touch cancel")]
465    fn unexpected_events(e: FidlTouchEvent) {
466        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
467        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
468            10.0,
469            20.0,
470            FidlEventPhase::Add,
471            1,
472        )]);
473        let batch = converter.handle(vec![e]);
474        assert_eq!(batch.events, vec![]);
475        assert_eq!(batch.last_event_time_ns, 0);
476        assert_eq!(batch.count_converted_fidl_events, 0);
477        assert_eq!(batch.count_ignored_fidl_events, 0);
478        assert_eq!(batch.count_unexpected_fidl_events, 1);
479    }
480
481    #[test]
482    fn touch_add() {
483        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
484        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
485            10.0,
486            20.0,
487            FidlEventPhase::Add,
488            1,
489        )]);
490
491        assert_eq!(
492            batch.events,
493            vec![
494                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
495                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
496                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
497                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
498                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
499                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
500            ]
501        );
502        assert_eq!(batch.last_event_time_ns, 0);
503        assert_eq!(batch.count_converted_fidl_events, 1);
504        assert_eq!(batch.count_ignored_fidl_events, 0);
505        assert_eq!(batch.count_unexpected_fidl_events, 0);
506
507        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
508        want_converter.pointer_id_to_slot_id.insert(1, 0);
509        want_converter
510            .pointer_id_to_event
511            .insert(1, make_internal_touch_event(0, 10, 20, FidlEventPhase::Add, 1));
512
513        assert_eq!(converter, want_converter);
514    }
515
516    #[test]
517    fn touch_change() {
518        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
519        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
520            10.0,
521            20.0,
522            FidlEventPhase::Add,
523            1,
524        )]);
525
526        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
527            11.0,
528            21.0,
529            FidlEventPhase::Change,
530            1,
531        )]);
532        assert_eq!(
533            batch.events,
534            vec![
535                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
536                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11),
537                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21),
538                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
539            ]
540        );
541        assert_eq!(batch.last_event_time_ns, 0);
542        assert_eq!(batch.count_converted_fidl_events, 1);
543        assert_eq!(batch.count_ignored_fidl_events, 0);
544        assert_eq!(batch.count_unexpected_fidl_events, 0);
545
546        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
547
548        want_converter.pointer_id_to_slot_id.insert(1, 0);
549        want_converter
550            .pointer_id_to_event
551            .insert(1, make_internal_touch_event(0, 11, 21, FidlEventPhase::Change, 1));
552
553        assert_eq!(converter, want_converter);
554    }
555
556    #[test]
557    fn touch_change_no_change() {
558        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
559        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
560            10.0,
561            20.0,
562            FidlEventPhase::Add,
563            1,
564        )]);
565
566        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
567            10.0,
568            20.0,
569            FidlEventPhase::Change,
570            1,
571        )]);
572        assert_eq!(batch.events, vec![]);
573        assert_eq!(batch.last_event_time_ns, 0);
574        assert_eq!(batch.count_converted_fidl_events, 1);
575        assert_eq!(batch.count_ignored_fidl_events, 0);
576        assert_eq!(batch.count_unexpected_fidl_events, 0);
577
578        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
579
580        want_converter.pointer_id_to_slot_id.insert(1, 0);
581        want_converter
582            .pointer_id_to_event
583            .insert(1, make_internal_touch_event(0, 10, 20, FidlEventPhase::Change, 1));
584
585        assert_eq!(converter, want_converter);
586    }
587
588    #[test]
589    fn touch_remove() {
590        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
591        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
592            10.0,
593            20.0,
594            FidlEventPhase::Add,
595            1,
596        )]);
597        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
598            0.0,
599            0.0,
600            FidlEventPhase::Remove,
601            1,
602        )]);
603        assert_eq!(batch.last_event_time_ns, 0);
604        assert_eq!(batch.count_converted_fidl_events, 1);
605        assert_eq!(batch.count_ignored_fidl_events, 0);
606        assert_eq!(batch.count_unexpected_fidl_events, 0);
607
608        assert_eq!(
609            batch.events,
610            vec![
611                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
612                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
613                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
614                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
615            ]
616        );
617
618        assert_eq!(
619            converter,
620            FuchsiaTouchEventToLinuxTouchEventConverter {
621                pointer_id_to_slot_id: SortedVecMap::new(),
622                pointer_id_to_event: SortedVecMap::new()
623            }
624        );
625    }
626
627    #[test]
628    fn multi_touch_sequence() {
629        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
630
631        // The first pointer down.
632        let _ = converter.handle(vec![make_touch_event_with_coords_phase_id(
633            10.0,
634            20.0,
635            FidlEventPhase::Add,
636            1,
637        )]);
638
639        // The second pointer down, and the first pointer move.
640        let batch = converter.handle(vec![
641            make_touch_event_with_coords_phase_id(11.0, 21.0, FidlEventPhase::Change, 1),
642            make_touch_event_with_coords_phase_id(100.0, 200.0, FidlEventPhase::Add, 2),
643        ]);
644
645        assert_eq!(
646            batch.events,
647            vec![
648                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
649                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11),
650                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21),
651                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
652                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
653                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 100),
654                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 200),
655                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
656            ]
657        );
658        assert_eq!(batch.last_event_time_ns, 0);
659        assert_eq!(batch.count_converted_fidl_events, 2);
660        assert_eq!(batch.count_ignored_fidl_events, 0);
661        assert_eq!(batch.count_unexpected_fidl_events, 0);
662
663        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
664
665        want_converter.pointer_id_to_slot_id.insert(1, 0);
666        want_converter.pointer_id_to_slot_id.insert(2, 1);
667        want_converter
668            .pointer_id_to_event
669            .insert(1, make_internal_touch_event(0, 11, 21, FidlEventPhase::Change, 1));
670        want_converter
671            .pointer_id_to_event
672            .insert(2, make_internal_touch_event(0, 100, 200, FidlEventPhase::Add, 2));
673
674        assert_eq!(converter, want_converter);
675
676        // Both pointer move.
677        let batch = converter.handle(vec![
678            make_touch_event_with_coords_phase_id(12.0, 22.0, FidlEventPhase::Change, 1),
679            make_touch_event_with_coords_phase_id(101.0, 201.0, FidlEventPhase::Change, 2),
680        ]);
681
682        assert_eq!(
683            batch.events,
684            vec![
685                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
686                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 12),
687                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 22),
688                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
689                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 101),
690                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 201),
691                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
692            ]
693        );
694        assert_eq!(batch.last_event_time_ns, 0);
695        assert_eq!(batch.count_converted_fidl_events, 2);
696        assert_eq!(batch.count_ignored_fidl_events, 0);
697        assert_eq!(batch.count_unexpected_fidl_events, 0);
698
699        want_converter
700            .pointer_id_to_event
701            .insert(1, make_internal_touch_event(0, 12, 22, FidlEventPhase::Change, 1));
702        want_converter
703            .pointer_id_to_event
704            .insert(2, make_internal_touch_event(0, 101, 201, FidlEventPhase::Change, 2));
705        assert_eq!(converter, want_converter);
706
707        // The second pointer up, and the first pointer move.
708        let batch = converter.handle(vec![
709            make_touch_event_with_coords_phase_id(13.0, 23.0, FidlEventPhase::Change, 1),
710            make_touch_event_with_coords_phase_id(0.0, 0.0, FidlEventPhase::Remove, 2),
711        ]);
712
713        assert_eq!(
714            batch.events,
715            vec![
716                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
717                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 13),
718                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 23),
719                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
720                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
721                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
722            ]
723        );
724        assert_eq!(batch.last_event_time_ns, 0);
725        assert_eq!(batch.count_converted_fidl_events, 2);
726        assert_eq!(batch.count_ignored_fidl_events, 0);
727        assert_eq!(batch.count_unexpected_fidl_events, 0);
728
729        want_converter
730            .pointer_id_to_event
731            .insert(1, make_internal_touch_event(0, 13, 23, FidlEventPhase::Change, 1));
732        want_converter.pointer_id_to_slot_id.remove(&2);
733        want_converter.pointer_id_to_event.remove(&2);
734
735        assert_eq!(converter, want_converter);
736
737        // The third pointer down, and the first pointer move.
738        let batch = converter.handle(vec![
739            make_touch_event_with_coords_phase_id(14.0, 24.0, FidlEventPhase::Change, 1),
740            make_touch_event_with_coords_phase_id(50.0, 60.0, FidlEventPhase::Add, 3),
741        ]);
742
743        assert_eq!(
744            batch.events,
745            vec![
746                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
747                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 14),
748                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 24),
749                // should reuse slot id 1.
750                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
751                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 3),
752                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 50),
753                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 60),
754                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
755            ]
756        );
757        assert_eq!(batch.last_event_time_ns, 0);
758        assert_eq!(batch.count_converted_fidl_events, 2);
759        assert_eq!(batch.count_ignored_fidl_events, 0);
760        assert_eq!(batch.count_unexpected_fidl_events, 0);
761
762        want_converter.pointer_id_to_slot_id.insert(3, 1);
763        want_converter
764            .pointer_id_to_event
765            .insert(1, make_internal_touch_event(0, 14, 24, FidlEventPhase::Change, 1));
766        want_converter
767            .pointer_id_to_event
768            .insert(3, make_internal_touch_event(0, 50, 60, FidlEventPhase::Add, 3));
769
770        assert_eq!(converter, want_converter);
771
772        // The third pointer up, and the first pointer move.
773        let batch = converter.handle(vec![
774            make_touch_event_with_coords_phase_id(15.0, 25.0, FidlEventPhase::Change, 1),
775            make_touch_event_with_coords_phase_id(0.0, 0.0, FidlEventPhase::Remove, 3),
776        ]);
777
778        assert_eq!(
779            batch.events,
780            vec![
781                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
782                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 15),
783                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 25),
784                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
785                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
786                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
787            ]
788        );
789        assert_eq!(batch.last_event_time_ns, 0);
790        assert_eq!(batch.count_converted_fidl_events, 2);
791        assert_eq!(batch.count_ignored_fidl_events, 0);
792        assert_eq!(batch.count_unexpected_fidl_events, 0);
793
794        want_converter
795            .pointer_id_to_event
796            .insert(1, make_internal_touch_event(0, 15, 25, FidlEventPhase::Change, 1));
797        want_converter.pointer_id_to_slot_id.remove(&3);
798        want_converter.pointer_id_to_event.remove(&3);
799
800        assert_eq!(converter, want_converter);
801
802        // The first pointer up.
803        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
804            0.0,
805            0.0,
806            FidlEventPhase::Remove,
807            1,
808        )]);
809
810        assert_eq!(
811            batch.events,
812            vec![
813                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
814                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, -1),
815                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 0),
816                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
817            ]
818        );
819        assert_eq!(batch.last_event_time_ns, 0);
820        assert_eq!(batch.count_converted_fidl_events, 1);
821        assert_eq!(batch.count_ignored_fidl_events, 0);
822        assert_eq!(batch.count_unexpected_fidl_events, 0);
823
824        want_converter.reset_state();
825        assert_eq!(converter, want_converter);
826    }
827
828    #[test]
829    fn multi_touch_sequence_receive_only_one_pointer_change_when_two_pointer_contacting() {
830        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
831
832        // 2 pointer down.
833        let batch = converter.handle(vec![
834            make_touch_event_with_coords_phase_id(10.0, 20.0, FidlEventPhase::Add, 1),
835            make_touch_event_with_coords_phase_id(100.0, 200.0, FidlEventPhase::Add, 2),
836        ]);
837
838        assert_eq!(
839            batch.events,
840            vec![
841                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
842                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1),
843                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10),
844                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20),
845                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 1),
846                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 2),
847                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 100),
848                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 200),
849                make_uapi_input_event(uapi::EV_KEY, uapi::BTN_TOUCH, 1),
850                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
851            ]
852        );
853        assert_eq!(batch.last_event_time_ns, 0);
854        assert_eq!(batch.count_converted_fidl_events, 2);
855        assert_eq!(batch.count_ignored_fidl_events, 0);
856        assert_eq!(batch.count_unexpected_fidl_events, 0);
857
858        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
859
860        want_converter.pointer_id_to_slot_id.insert(1, 0);
861        want_converter.pointer_id_to_slot_id.insert(2, 1);
862        want_converter
863            .pointer_id_to_event
864            .insert(1, make_internal_touch_event(0, 10, 20, FidlEventPhase::Add, 1));
865        want_converter
866            .pointer_id_to_event
867            .insert(2, make_internal_touch_event(0, 100, 200, FidlEventPhase::Add, 2));
868        assert_eq!(converter, want_converter);
869
870        // 1st pointer move, no event for 2nd pointer.
871        let batch = converter.handle(vec![make_touch_event_with_coords_phase_id(
872            12.0,
873            22.0,
874            FidlEventPhase::Change,
875            1,
876        )]);
877
878        assert_eq!(
879            batch.events,
880            vec![
881                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0),
882                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 12),
883                make_uapi_input_event(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 22),
884                make_uapi_input_event(uapi::EV_SYN, uapi::SYN_REPORT, 0),
885            ]
886        );
887        assert_eq!(batch.last_event_time_ns, 0);
888        assert_eq!(batch.count_converted_fidl_events, 1);
889        assert_eq!(batch.count_ignored_fidl_events, 0);
890        assert_eq!(batch.count_unexpected_fidl_events, 0);
891
892        want_converter
893            .pointer_id_to_event
894            .insert(1, make_internal_touch_event(0, 12, 22, FidlEventPhase::Change, 1));
895        assert_eq!(converter, want_converter);
896    }
897
898    #[test]
899    fn handle_return_multi_protocl_b_seq() {
900        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
901
902        let batch = converter.handle(vec![
903            // ignore
904            FidlTouchEvent::default(),
905            make_touch_event_with_coords_phase_id_time(10.0, 20.0, FidlEventPhase::Add, 1, 1),
906            make_touch_event_with_coords_phase_id_time(11.0, 21.0, FidlEventPhase::Change, 1, 1000),
907        ]);
908
909        assert_eq!(
910            batch.events,
911            vec![
912                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1),
913                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1, 1),
914                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10, 1),
915                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20, 1),
916                make_uapi_input_event_with_time(uapi::EV_KEY, uapi::BTN_TOUCH, 1, 1),
917                make_uapi_input_event_with_time(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1),
918                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1000),
919                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11, 1000),
920                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21, 1000),
921                make_uapi_input_event_with_time(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1000),
922            ]
923        );
924        assert_eq!(batch.last_event_time_ns, 1000);
925        assert_eq!(batch.count_converted_fidl_events, 2);
926        assert_eq!(batch.count_ignored_fidl_events, 1);
927        assert_eq!(batch.count_unexpected_fidl_events, 0);
928
929        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
930
931        want_converter.pointer_id_to_slot_id.insert(1, 0);
932        want_converter
933            .pointer_id_to_event
934            .insert(1, make_internal_touch_event(1000, 11, 21, FidlEventPhase::Change, 1));
935
936        assert_eq!(converter, want_converter);
937    }
938
939    #[test]
940    fn handle_unsorted_events() {
941        let mut converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
942
943        let batch = converter.handle(vec![
944            // ignore
945            FidlTouchEvent::default(),
946            make_touch_event_with_coords_phase_id_time(11.0, 21.0, FidlEventPhase::Change, 1, 1000),
947            make_touch_event_with_coords_phase_id_time(10.0, 20.0, FidlEventPhase::Add, 1, 1),
948        ]);
949
950        assert_eq!(
951            batch.events,
952            vec![
953                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1),
954                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_TRACKING_ID, 1, 1),
955                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 10, 1),
956                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 20, 1),
957                make_uapi_input_event_with_time(uapi::EV_KEY, uapi::BTN_TOUCH, 1, 1),
958                make_uapi_input_event_with_time(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1),
959                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_SLOT, 0, 1000),
960                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_X, 11, 1000),
961                make_uapi_input_event_with_time(uapi::EV_ABS, uapi::ABS_MT_POSITION_Y, 21, 1000),
962                make_uapi_input_event_with_time(uapi::EV_SYN, uapi::SYN_REPORT, 0, 1000),
963            ]
964        );
965        assert_eq!(batch.last_event_time_ns, 1000);
966        assert_eq!(batch.count_converted_fidl_events, 2);
967        assert_eq!(batch.count_ignored_fidl_events, 1);
968        assert_eq!(batch.count_unexpected_fidl_events, 0);
969
970        let mut want_converter = FuchsiaTouchEventToLinuxTouchEventConverter::create();
971
972        want_converter.pointer_id_to_slot_id.insert(1, 0);
973        want_converter
974            .pointer_id_to_event
975            .insert(1, make_internal_touch_event(1000, 11, 21, FidlEventPhase::Change, 1));
976
977        assert_eq!(converter, want_converter);
978    }
979}