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