Skip to main content

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