Skip to main content

input_pipeline_dso/
text_settings_handler.rs

1// Copyright 2021 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_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
6use crate::{input_device, metrics};
7use anyhow::{Context, Error, Result};
8use async_trait::async_trait;
9use async_utils::hanging_get::client::HangingGetStream;
10use fidl_fuchsia_input as finput;
11use fidl_fuchsia_settings as fsettings;
12use fuchsia_async as fasync;
13use fuchsia_inspect::health::Reporter;
14use futures::{TryFutureExt, TryStreamExt};
15use metrics_registry::*;
16use std::cell::RefCell;
17use std::rc::Rc;
18
19/// The text settings handler instance. Refer to as `text_settings_handler::TextSettingsHandler`.
20/// Its task is to decorate an input event with the keymap identifier.  The instance can
21/// be freely cloned, each clone is thread-safely sharing data with others.
22pub struct TextSettingsHandler {
23    /// Stores the currently active keymap identifier, if present.  Wrapped
24    /// in an refcell as it can be changed out of band through
25    /// `fuchsia.input.keymap.Configuration/SetLayout`.
26    keymap_id: RefCell<Option<finput::KeymapId>>,
27
28    /// The inventory of this handler's Inspect status.
29    pub inspect_status: InputHandlerStatus,
30
31    /// The metrics logger.
32    metrics_logger: metrics::MetricsLogger,
33}
34
35impl Handler for TextSettingsHandler {
36    fn set_handler_healthy(self: std::rc::Rc<Self>) {
37        self.inspect_status.health_node.borrow_mut().set_ok();
38    }
39
40    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
41        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
42    }
43
44    fn get_name(&self) -> &'static str {
45        "TextSettingsHandler"
46    }
47
48    fn interest(&self) -> Vec<input_device::InputEventType> {
49        vec![input_device::InputEventType::Keyboard]
50    }
51}
52
53#[async_trait(?Send)]
54impl UnhandledInputHandler for TextSettingsHandler {
55    async fn handle_unhandled_input_event(
56        self: Rc<Self>,
57        unhandled_input_event: input_device::UnhandledInputEvent,
58    ) -> Vec<input_device::InputEvent> {
59        fuchsia_trace::duration!("input", "text_settings_handler");
60        match unhandled_input_event {
61            input_device::UnhandledInputEvent {
62                device_event: input_device::InputDeviceEvent::Keyboard(mut event),
63                device_descriptor,
64                event_time,
65                trace_id,
66            } => {
67                fuchsia_trace::duration!("input", "text_settings_handler[processing]");
68                if let Some(trace_id) = trace_id {
69                    fuchsia_trace::flow_step!(
70                        c"input",
71                        c"event_in_input_pipeline",
72                        trace_id.into()
73                    );
74                }
75
76                self.inspect_status.count_received_event(&event_time);
77                let keymap_id = self.get_keymap_name();
78                log::debug!(
79                    "text_settings_handler::Instance::handle_unhandled_input_event: keymap_id = {:?}",
80                    &keymap_id
81                );
82                event = event.into_with_keymap(keymap_id);
83                vec![input_device::InputEvent {
84                    device_event: input_device::InputDeviceEvent::Keyboard(event),
85                    device_descriptor,
86                    event_time,
87                    handled: input_device::Handled::No,
88                    trace_id,
89                }]
90            }
91            // Pass a non-keyboard event through.
92            _ => {
93                self.metrics_logger.log_error(
94                    InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
95                    std::format!(
96                        "{} uninterested input event: {:?}",
97                        self.get_name(),
98                        unhandled_input_event.get_event_type()
99                    ),
100                );
101                vec![input_device::InputEvent::from(unhandled_input_event)]
102            }
103        }
104    }
105}
106
107impl TextSettingsHandler {
108    /// Creates a new text settings handler instance.
109    ///
110    /// `initial_*` contain the desired initial values to be served.  Usually
111    /// you want the defaults.
112    pub fn new(
113        initial_keymap: Option<finput::KeymapId>,
114        input_handlers_node: &fuchsia_inspect::Node,
115        metrics_logger: metrics::MetricsLogger,
116    ) -> Rc<Self> {
117        let inspect_status = InputHandlerStatus::new(
118            input_handlers_node,
119            "text_settings_handler",
120            /* generates_events */ false,
121        );
122        Rc::new(Self { keymap_id: RefCell::new(initial_keymap), inspect_status, metrics_logger })
123    }
124
125    /// Processes requests for keymap change from `stream`.
126    pub async fn process_keymap_configuration_from(
127        self: &Rc<Self>,
128        proxy: fsettings::KeyboardProxy,
129    ) -> Result<(), Error> {
130        let mut stream = HangingGetStream::new(proxy, fsettings::KeyboardProxy::watch);
131        loop {
132            match stream
133                .try_next()
134                .await
135                .context("while waiting on fuchsia.settings.Keyboard/Watch")?
136            {
137                Some(fsettings::KeyboardSettings { keymap, .. }) => {
138                    self.set_keymap_id(keymap);
139                    log::info!("keymap ID set to: {:?}", self.get_keymap_id());
140                }
141                e => {
142                    self.metrics_logger.log_error(
143                        InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerExit,
144                        std::format!("exiting - unexpected response: {:?}", e),
145                    );
146                    break;
147                }
148            }
149        }
150        Ok(())
151    }
152
153    /// Starts reading events from the stream.  Does not block.
154    pub fn serve(self: Rc<Self>, proxy: fsettings::KeyboardProxy) {
155        let metrics_logger_clone = self.metrics_logger.clone();
156        fasync::Task::local(
157            async move { self.process_keymap_configuration_from(proxy).await }
158                // In most tests, this message is not fatal. It indicates that keyboard
159                // settings won't run, but that only means you can't configure keyboard
160                // settings. If your tests does not need to change keymaps, or adjust
161                // key autorepeat rates, these are not relevant.
162                .unwrap_or_else(move |e: anyhow::Error| {
163                    metrics_logger_clone.log_warn(
164                        InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerCantRun,
165                        std::format!("can't run: {:?}", e),
166                    );
167                }),
168        )
169        .detach();
170    }
171
172    fn set_keymap_id(self: &Rc<Self>, keymap_id: Option<finput::KeymapId>) {
173        *(self.keymap_id.borrow_mut()) = keymap_id;
174    }
175
176    /// Gets the currently active keymap ID.
177    pub fn get_keymap_id(&self) -> Option<finput::KeymapId> {
178        self.keymap_id.borrow().clone()
179    }
180
181    fn get_keymap_name(&self) -> Option<String> {
182        // Maybe instead just pass in the keymap ID directly?
183        match *self.keymap_id.borrow() {
184            Some(id) => match id {
185                finput::KeymapId::FrAzerty => Some("FR_AZERTY".to_owned()),
186                finput::KeymapId::UsDvorak => Some("US_DVORAK".to_owned()),
187                finput::KeymapId::UsColemak => Some("US_COLEMAK".to_owned()),
188                finput::KeymapId::UsQwerty | finput::KeymapIdUnknown!() => {
189                    Some("US_QWERTY".to_owned())
190                }
191            },
192            None => Some("US_QWERTY".to_owned()),
193        }
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    use crate::input_handler::InputHandler;
202    use crate::{keyboard_binding, testing_utilities};
203    use fuchsia_async as fasync;
204    use pretty_assertions::assert_eq;
205    use std::convert::TryFrom as _;
206
207    fn input_event_from(
208        keyboard_event: keyboard_binding::KeyboardEvent,
209    ) -> input_device::InputEvent {
210        testing_utilities::create_input_event(
211            keyboard_event,
212            &input_device::InputDeviceDescriptor::Fake,
213            zx::MonotonicInstant::from_nanos(42),
214            input_device::Handled::No,
215        )
216    }
217
218    fn key_event_with_settings(keymap: Option<String>) -> input_device::InputEvent {
219        let keyboard_event = keyboard_binding::KeyboardEvent::new(
220            fidl_fuchsia_input::Key::A,
221            fidl_fuchsia_ui_input3::KeyEventType::Pressed,
222        )
223        .into_with_keymap(keymap);
224        input_event_from(keyboard_event)
225    }
226
227    fn key_event(keymap: Option<String>) -> input_device::InputEvent {
228        let keyboard_event = keyboard_binding::KeyboardEvent::new(
229            fidl_fuchsia_input::Key::A,
230            fidl_fuchsia_ui_input3::KeyEventType::Pressed,
231        )
232        .into_with_keymap(keymap);
233        input_event_from(keyboard_event)
234    }
235
236    fn unhandled_key_event() -> input_device::UnhandledInputEvent {
237        input_device::UnhandledInputEvent::try_from(key_event(None)).unwrap()
238    }
239
240    #[fasync::run_singlethreaded(test)]
241    async fn keymap_id_setting() {
242        #[derive(Debug)]
243        struct Test {
244            keymap_id: Option<finput::KeymapId>,
245            expected: Option<String>,
246        }
247        let tests = vec![
248            Test { keymap_id: None, expected: Some("US_QWERTY".to_owned()) },
249            Test {
250                keymap_id: Some(finput::KeymapId::UsQwerty),
251                expected: Some("US_QWERTY".to_owned()),
252            },
253            Test {
254                keymap_id: Some(finput::KeymapId::FrAzerty),
255                expected: Some("FR_AZERTY".to_owned()),
256            },
257            Test {
258                keymap_id: Some(finput::KeymapId::UsDvorak),
259                expected: Some("US_DVORAK".to_owned()),
260            },
261            Test {
262                keymap_id: Some(finput::KeymapId::UsColemak),
263                expected: Some("US_COLEMAK".to_owned()),
264            },
265        ];
266        let inspector = fuchsia_inspect::Inspector::default();
267        let test_node = inspector.root().create_child("test_node");
268        for test in tests {
269            let handler = TextSettingsHandler::new(
270                test.keymap_id.clone(),
271                &test_node,
272                metrics::MetricsLogger::default(),
273            );
274            let expected = key_event(test.expected.clone());
275            let result = handler.handle_unhandled_input_event(unhandled_key_event()).await;
276            assert_eq!(vec![expected], result, "for: {:?}", &test);
277        }
278    }
279
280    fn serve_into(
281        mut server_end: fsettings::KeyboardRequestStream,
282        keymap: Option<finput::KeymapId>,
283    ) {
284        fasync::Task::local(async move {
285            if let Ok(Some(fsettings::KeyboardRequest::Watch { responder, .. })) =
286                server_end.try_next().await
287            {
288                let settings = fsettings::KeyboardSettings { keymap, ..Default::default() };
289                responder.send(&settings).expect("response sent");
290            }
291        })
292        .detach();
293    }
294
295    #[fasync::run_singlethreaded(test)]
296    async fn config_call_processing() {
297        let inspector = fuchsia_inspect::Inspector::default();
298        let test_node = inspector.root().create_child("test_node");
299        let handler = TextSettingsHandler::new(None, &test_node, metrics::MetricsLogger::default());
300
301        let (proxy, stream) =
302            fidl::endpoints::create_proxy_and_stream::<fsettings::KeyboardMarker>();
303
304        // Serve a specific keyboard setting.
305        serve_into(stream, Some(finput::KeymapId::FrAzerty));
306
307        // Start an asynchronous handler that processes keymap configuration calls
308        // incoming from `server_end`.
309        handler.clone().serve(proxy);
310
311        // Setting the keymap with a hanging get that does not synchronize with the "main"
312        // task of the handler inherently races with `handle_input_event`.  So, the only
313        // way to test it correctly is to verify that we get a changed setting *eventually*
314        // after asking the server to hand out the modified settings.  So, we loop with an
315        // expectation that at some point the settings get applied.  To avoid a long timeout
316        // we quit the loop if nothing happened after a generous amount of time.
317        let deadline =
318            fuchsia_async::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(5));
319        loop {
320            let result = handler.clone().handle_unhandled_input_event(unhandled_key_event()).await;
321            let expected = key_event_with_settings(Some("FR_AZERTY".to_owned()));
322            if vec![expected] == result {
323                break;
324            }
325            fuchsia_async::Timer::new(fuchsia_async::MonotonicInstant::after(
326                zx::MonotonicDuration::from_millis(10),
327            ))
328            .await;
329            let now = fuchsia_async::MonotonicInstant::now();
330            assert!(now < deadline, "the settings did not get applied, was: {:?}", &result);
331        }
332    }
333
334    #[fuchsia::test]
335    async fn text_settings_handler_initialized_with_inspect_node() {
336        let inspector = fuchsia_inspect::Inspector::default();
337        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
338        let _handler =
339            TextSettingsHandler::new(None, &fake_handlers_node, metrics::MetricsLogger::default());
340        diagnostics_assertions::assert_data_tree!(inspector, root: {
341            input_handlers_node: {
342                text_settings_handler: {
343                    events_received_count: 0u64,
344                    events_handled_count: 0u64,
345                    last_received_timestamp_ns: 0u64,
346                    "fuchsia.inspect.Health": {
347                        status: "STARTING_UP",
348                        // Timestamp value is unpredictable and not relevant in this context,
349                        // so we only assert that the property is present.
350                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
351                    },
352                }
353            }
354        });
355    }
356
357    #[fasync::run_singlethreaded(test)]
358    async fn text_settings_handler_inspect_counts_events() {
359        let inspector = fuchsia_inspect::Inspector::default();
360        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
361        let text_settings_handler =
362            TextSettingsHandler::new(None, &fake_handlers_node, metrics::MetricsLogger::default());
363        let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
364            keyboard_binding::KeyboardDeviceDescriptor {
365                keys: vec![finput::Key::A, finput::Key::B],
366                ..Default::default()
367            },
368        );
369        let (_, event_time_u64) = testing_utilities::event_times();
370        let input_events = vec![
371            testing_utilities::create_keyboard_event_with_time(
372                finput::Key::A,
373                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
374                None,
375                event_time_u64,
376                &device_descriptor,
377                /* keymap= */ None,
378            ),
379            // Should not count received events that have already been handled.
380            testing_utilities::create_keyboard_event_with_handled(
381                finput::Key::B,
382                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
383                None,
384                event_time_u64,
385                &device_descriptor,
386                /* keymap= */ None,
387                /* key_meaning= */ None,
388                input_device::Handled::Yes,
389            ),
390            testing_utilities::create_keyboard_event_with_time(
391                finput::Key::A,
392                fidl_fuchsia_ui_input3::KeyEventType::Released,
393                None,
394                event_time_u64,
395                &device_descriptor,
396                /* keymap= */ None,
397            ),
398            // Should not count non-keyboard input events.
399            testing_utilities::create_fake_input_event(event_time_u64),
400            testing_utilities::create_keyboard_event_with_time(
401                finput::Key::B,
402                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
403                None,
404                event_time_u64,
405                &device_descriptor,
406                /* keymap= */ None,
407            ),
408        ];
409
410        for input_event in input_events {
411            let _ = text_settings_handler.clone().handle_input_event(input_event).await;
412        }
413
414        let last_event_timestamp: u64 = event_time_u64.into_nanos().try_into().unwrap();
415
416        diagnostics_assertions::assert_data_tree!(inspector, root: {
417            input_handlers_node: {
418                text_settings_handler: {
419                    events_received_count: 3u64,
420                    events_handled_count: 0u64,
421                    last_received_timestamp_ns: last_event_timestamp,
422                    "fuchsia.inspect.Health": {
423                        status: "STARTING_UP",
424                        // Timestamp value is unpredictable and not relevant in this context,
425                        // so we only assert that the property is present.
426                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
427                    },
428                }
429            }
430        });
431    }
432}