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