Skip to main content

input_testing/
input_device_registry.rs

1// Copyright 2022 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 crate::input_device::InputDevice;
6use crate::new_fake_device_info;
7use anyhow::{Context as _, Error};
8use async_utils::event::Event as AsyncEvent;
9use fidl::endpoints;
10use fidl_fuchsia_input::Key;
11use fidl_fuchsia_input_injection::InputDeviceRegistryProxy;
12use fidl_fuchsia_input_report::{
13    Axis, ConsumerControlButton, ConsumerControlDescriptor, ConsumerControlInputDescriptor,
14    ContactInputDescriptor, DeviceDescriptor, InputDeviceMarker, KeyboardDescriptor,
15    KeyboardInputDescriptor, MouseDescriptor, MouseInputDescriptor, Range, TOUCH_MAX_CONTACTS,
16    TouchDescriptor, TouchInputDescriptor, TouchType, Unit, UnitType,
17};
18use fidl_fuchsia_ui_test_input::MouseButton;
19
20/// Implements the client side of the `fuchsia.input.injection.InputDeviceRegistry` protocol.
21pub(crate) struct InputDeviceRegistry {
22    proxy: InputDeviceRegistryProxy,
23    got_input_reports_reader: AsyncEvent,
24}
25
26impl InputDeviceRegistry {
27    pub fn new(proxy: InputDeviceRegistryProxy, got_input_reports_reader: AsyncEvent) -> Self {
28        Self { proxy, got_input_reports_reader }
29    }
30
31    /// Registers a touchscreen device, with in injection coordinate space that spans [-1000, 1000]
32    /// on both axes.
33    /// # Returns
34    /// A `input_device::InputDevice`, which can be used to send events to the
35    /// `fuchsia.input.report.InputDevice` that has been registered with the
36    /// `fuchsia.input.injection.InputDeviceRegistry` service.
37    pub async fn add_touchscreen_device(
38        &mut self,
39        min_x: i64,
40        max_x: i64,
41        min_y: i64,
42        max_y: i64,
43    ) -> Result<InputDevice, Error> {
44        self.add_device(DeviceDescriptor {
45            touch: Some(TouchDescriptor {
46                input: Some(TouchInputDescriptor {
47                    contacts: Some(
48                        std::iter::repeat(ContactInputDescriptor {
49                            position_x: Some(Axis {
50                                range: Range { min: min_x, max: max_x },
51                                unit: Unit { type_: UnitType::Other, exponent: 0 },
52                            }),
53                            position_y: Some(Axis {
54                                range: Range { min: min_y, max: max_y },
55                                unit: Unit { type_: UnitType::Other, exponent: 0 },
56                            }),
57                            contact_width: Some(Axis {
58                                range: Range { min: min_x, max: max_x },
59                                unit: Unit { type_: UnitType::Other, exponent: 0 },
60                            }),
61                            contact_height: Some(Axis {
62                                range: Range { min: min_y, max: max_y },
63                                unit: Unit { type_: UnitType::Other, exponent: 0 },
64                            }),
65                            ..Default::default()
66                        })
67                        .take(
68                            usize::try_from(TOUCH_MAX_CONTACTS)
69                                .context("usize is impossibly small")?,
70                        )
71                        .collect(),
72                    ),
73                    max_contacts: Some(TOUCH_MAX_CONTACTS),
74                    touch_type: Some(TouchType::Touchscreen),
75                    buttons: Some(vec![]),
76                    ..Default::default()
77                }),
78                ..Default::default()
79            }),
80            ..Default::default()
81        })
82        .await
83    }
84
85    /// Registers a media buttons device.
86    /// # Returns
87    /// A `input_device::InputDevice`, which can be used to send events to the
88    /// `fuchsia.input.report.InputDevice` that has been registered with the
89    /// `fuchsia.input.injection.InputDeviceRegistry` service.
90    pub async fn add_media_buttons_device(&mut self) -> Result<InputDevice, Error> {
91        self.add_device(DeviceDescriptor {
92            consumer_control: Some(ConsumerControlDescriptor {
93                input: Some(ConsumerControlInputDescriptor {
94                    buttons: Some(vec![
95                        ConsumerControlButton::VolumeUp,
96                        ConsumerControlButton::VolumeDown,
97                        ConsumerControlButton::Pause,
98                        ConsumerControlButton::FactoryReset,
99                        ConsumerControlButton::MicMute,
100                        ConsumerControlButton::Reboot,
101                        ConsumerControlButton::CameraDisable,
102                        ConsumerControlButton::Power,
103                        ConsumerControlButton::Function,
104                    ]),
105                    ..Default::default()
106                }),
107                ..Default::default()
108            }),
109            ..Default::default()
110        })
111        .await
112    }
113
114    /// Registers a keyboard device.
115    /// # Returns
116    /// An `input_device::InputDevice`, which can be used to send events to the
117    /// `fuchsia.input.report.InputDevice` that has been registered with the
118    /// `fuchsia.input.injection.InputDeviceRegistry` service.
119    pub async fn add_keyboard_device(&mut self) -> Result<InputDevice, Error> {
120        // Generate a `Vec` of all known keys.
121        // * Because there is no direct way to iterate over enum values, we iterate
122        //   over the values corresponding to `Key::A` and `Key::MediaVolumeDecrement`.
123        // * Some values in the range have no corresponding enum value. For example,
124        //   the value 0x00070065 sits between `NonUsBackslash` (0x00070064), and
125        //   `KeypadEquals` (0x00070067). Such primitives are removed by `filter_map()`.
126        //
127        // TODO(https://fxbug.dev/42059900): Extend to include all values of the Key enum.
128        let all_keys: Vec<Key> = (Key::A.into_primitive()
129            ..=Key::MediaVolumeDecrement.into_primitive())
130            .filter_map(Key::from_primitive)
131            .collect();
132        self.add_device(DeviceDescriptor {
133            // Required for DeviceDescriptor.
134            device_information: Some(new_fake_device_info()),
135            keyboard: Some(KeyboardDescriptor {
136                input: Some(KeyboardInputDescriptor {
137                    keys3: Some(all_keys),
138                    ..Default::default()
139                }),
140                ..Default::default()
141            }),
142            ..Default::default()
143        })
144        .await
145    }
146
147    pub async fn add_mouse_device(&mut self) -> Result<InputDevice, Error> {
148        self.add_device(DeviceDescriptor {
149            // Required for DeviceDescriptor.
150            device_information: Some(new_fake_device_info()),
151            mouse: Some(MouseDescriptor {
152                input: Some(MouseInputDescriptor {
153                    movement_x: Some(Axis {
154                        range: Range { min: -1000, max: 1000 },
155                        unit: Unit { type_: UnitType::Other, exponent: 0 },
156                    }),
157                    movement_y: Some(Axis {
158                        range: Range { min: -1000, max: 1000 },
159                        unit: Unit { type_: UnitType::Other, exponent: 0 },
160                    }),
161                    // `scroll_v` and `scroll_h` are range of tick number on
162                    // driver's report. [-100, 100] should be enough for
163                    // testing.
164                    scroll_v: Some(Axis {
165                        range: Range { min: -100, max: 100 },
166                        unit: Unit { type_: UnitType::Other, exponent: 0 },
167                    }),
168                    scroll_h: Some(Axis {
169                        range: Range { min: -100, max: 100 },
170                        unit: Unit { type_: UnitType::Other, exponent: 0 },
171                    }),
172                    // Match to the values of fuchsia.ui.test.input.MouseButton.
173                    buttons: Some(
174                        (MouseButton::First.into_primitive()..=MouseButton::Third.into_primitive())
175                            .map(|b| {
176                                b.try_into().expect("failed to convert mouse button to primitive")
177                            })
178                            .collect(),
179                    ),
180                    position_x: None,
181                    position_y: None,
182                    ..Default::default()
183                }),
184                ..Default::default()
185            }),
186            ..Default::default()
187        })
188        .await
189    }
190
191    /// Adds a device to the `InputDeviceRegistry` FIDL server connected to this
192    /// `InputDeviceRegistry` struct.
193    ///
194    /// # Returns
195    /// A `input_device::InputDevice`, which can be used to send events to the
196    /// `fuchsia.input.report.InputDevice` that has been registered with the
197    /// `fuchsia.input.injection.InputDeviceRegistry` service.
198    async fn add_device(&self, descriptor: DeviceDescriptor) -> Result<InputDevice, Error> {
199        let (client_end, request_stream) = endpoints::create_request_stream::<InputDeviceMarker>();
200        let mut device: InputDevice =
201            InputDevice::new(request_stream, descriptor, self.got_input_reports_reader.clone());
202
203        let res = self.proxy.register_and_get_device_info(client_end).await?;
204        let device_id = res.device_id.expect("missing device_id");
205        device.device_id = device_id;
206
207        Ok(device)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use fidl_fuchsia_input_injection::{
215        InputDeviceRegistryMarker, InputDeviceRegistryRegisterAndGetDeviceInfoResponse,
216        InputDeviceRegistryRequest,
217    };
218    use fidl_fuchsia_input_report::InputReportsReaderV2Marker;
219    use futures::StreamExt;
220    use test_case::test_case;
221
222    const MAX_UNACKNOWLEDGED_REPORTS_LIMIT: u16 = 120;
223
224    enum TestDeviceType {
225        TouchScreen,
226        MediaButtons,
227        Keyboard,
228        Mouse,
229    }
230
231    async fn add_device_for_test(
232        registry: &mut InputDeviceRegistry,
233        ty: TestDeviceType,
234    ) -> Result<InputDevice, Error> {
235        match ty {
236            TestDeviceType::TouchScreen => registry.add_touchscreen_device(1, 1000, 1, 1000).await,
237            TestDeviceType::MediaButtons => registry.add_media_buttons_device().await,
238            TestDeviceType::Keyboard => registry.add_keyboard_device().await,
239            TestDeviceType::Mouse => registry.add_mouse_device().await,
240        }
241    }
242
243    #[test_case(TestDeviceType::TouchScreen =>
244                matches Ok(DeviceDescriptor {
245                    touch: Some(TouchDescriptor {
246                        input: Some(TouchInputDescriptor { .. }),
247                        ..
248                    }),
249                    .. });
250                "touchscreen_device")]
251    #[test_case(TestDeviceType::MediaButtons =>
252                matches Ok(DeviceDescriptor {
253                    consumer_control: Some(ConsumerControlDescriptor {
254                        input: Some(ConsumerControlInputDescriptor { .. }),
255                        ..
256                    }),
257                    .. });
258                "media_buttons_device")]
259    #[test_case(TestDeviceType::Keyboard =>
260                matches Ok(DeviceDescriptor {
261                    keyboard: Some(KeyboardDescriptor { .. }),
262                    ..
263                });
264                "keyboard_device")]
265    #[test_case(TestDeviceType::Mouse =>
266                matches Ok(DeviceDescriptor {
267                    mouse: Some(MouseDescriptor { .. }),
268                    ..
269                });
270                "mouse_device")]
271    #[fuchsia::test]
272    async fn add_device_registers_correct_device_type(
273        device_type: TestDeviceType,
274    ) -> Result<DeviceDescriptor, Error> {
275        let (registry_proxy, mut registry_request_stream) =
276            endpoints::create_proxy_and_stream::<InputDeviceRegistryMarker>();
277        let mut input_device_registry = InputDeviceRegistry {
278            proxy: registry_proxy,
279            got_input_reports_reader: AsyncEvent::new(),
280        };
281
282        let add_device_fut = add_device_for_test(&mut input_device_registry, device_type);
283
284        let input_device_proxy_fut = async {
285            // `input_device_registry` should send a `Register` messgage to `registry_request_stream`.
286            // Use `registry_request_stream` to grab the `ClientEnd` of the device added above,
287            // and convert the `ClientEnd` into an `InputDeviceProxy`.
288            //
289            // Here only handle InputDeviceRegistryRequest once.
290            let input_device_proxy = match registry_request_stream
291                .next()
292                .await
293                .expect("stream read should yield Some")
294                .expect("fidl read")
295            {
296                InputDeviceRegistryRequest::Register { .. } => {
297                    unreachable!("InputDeviceRegistryRequest::Register should not be called");
298                }
299                InputDeviceRegistryRequest::RegisterAndGetDeviceInfo {
300                    device, responder, ..
301                } => {
302                    responder
303                        .send(InputDeviceRegistryRegisterAndGetDeviceInfoResponse {
304                            device_id: Some(1),
305                            ..Default::default()
306                        })
307                        .expect("RegisterAndGetDeviceInfo send response failed");
308
309                    device
310                }
311            }
312            .into_proxy();
313
314            input_device_proxy
315        };
316
317        let (add_device_res, input_device_proxy) =
318            futures::join!(add_device_fut, input_device_proxy_fut);
319
320        let input_device = add_device_res.expect("add_device failed");
321        assert_ne!(input_device.device_id, 0);
322
323        let input_device_get_descriptor = input_device_proxy.get_descriptor().await;
324
325        let input_device_server_fut = input_device.flush();
326
327        // Avoid unrelated `panic()`: `InputDevice` requires clients to get an input
328        // reports reader, to help debug integration test failures where no component
329        // read events from the fake device.
330        let (_input_reports_reader_proxy, input_reports_reader_server_end) =
331            endpoints::create_proxy::<InputReportsReaderV2Marker>();
332        let _ = input_device_proxy
333            .get_input_reports_reader_v2(
334                input_reports_reader_server_end,
335                MAX_UNACKNOWLEDGED_REPORTS_LIMIT,
336            )
337            .await;
338
339        std::mem::drop(input_device_proxy); // Terminate stream served by `input_device_server_fut`.
340        input_device_server_fut.await;
341
342        input_device_get_descriptor.map_err(anyhow::Error::from)
343    }
344}