1use 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
17pub struct TextSettingsHandler {
21 keymap_id: RefCell<Option<finput::KeymapId>>,
25
26 pub inspect_status: InputHandlerStatus,
28
29 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 _ => {
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 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 false,
118 );
119 Rc::new(Self { keymap_id: RefCell::new(initial_keymap), inspect_status, metrics_logger })
120 }
121
122 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 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 .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 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 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_into(stream, Some(finput::KeymapId::FrAzerty));
303
304 handler.clone().serve(proxy);
307
308 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 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 None,
375 ),
376 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 None,
384 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 None,
394 ),
395 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 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 start_timestamp_nanos: diagnostics_assertions::AnyProperty
424 },
425 }
426 }
427 });
428 }
429}