input_pipeline/
focus_listener.rs

1// Copyright 2020 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::metrics;
6use anyhow::{Context, Error};
7use focus_chain_provider::FocusChainProviderPublisher;
8use fuchsia_component::client::connect_to_protocol;
9use futures::StreamExt;
10use metrics_registry::*;
11use {fidl_fuchsia_ui_focus as focus, fidl_fuchsia_ui_keyboard_focus as kbd_focus};
12
13/// FocusListener listens to focus change and notify to related input modules.
14pub struct FocusListener {
15    /// The FIDL proxy to text_manager.
16    text_manager: kbd_focus::ControllerProxy,
17
18    /// A channel that receives focus chain updates.
19    focus_chain_listener: focus::FocusChainListenerRequestStream,
20
21    /// Forwards focus chain updates to downstream watchers.
22    focus_chain_publisher: FocusChainProviderPublisher,
23
24    /// The metrics logger.
25    metrics_logger: metrics::MetricsLogger,
26}
27
28impl FocusListener {
29    /// Creates a new focus listener that holds proxy to text manager.
30    /// The caller is expected to spawn a task to continually listen to focus change event.
31    ///
32    /// # Arguments
33    /// - `focus_chain_publisher`: Allows focus chain updates to be sent to downstream listeners.
34    ///   Note that this is not required for `FocusListener` to function, so it could be made an
35    ///  `Option` in future changes.
36    ///
37    /// # Example
38    ///
39    /// ```ignore
40    /// let mut listener = FocusListener::new(focus_chain_publisher);
41    /// let task = fuchsia_async::Task::local(async move {
42    ///     listener.dispatch_focus_changes().await
43    /// });
44    /// ```
45    ///
46    /// # FIDL
47    ///
48    /// Required:
49    ///
50    /// - `fuchsia.ui.views.FocusChainListener`
51    /// - `fuchsia.ui.keyboard.focus.Controller`
52    ///
53    /// # Errors
54    /// If unable to connect to the text_manager protocol.
55    pub fn new(
56        focus_chain_publisher: FocusChainProviderPublisher,
57        metrics_logger: metrics::MetricsLogger,
58    ) -> Result<Self, Error> {
59        let text_manager = connect_to_protocol::<kbd_focus::ControllerMarker>()?;
60
61        let (focus_chain_listener_client_end, focus_chain_listener) =
62            fidl::endpoints::create_request_stream::<focus::FocusChainListenerMarker>();
63
64        let focus_chain_listener_registry: focus::FocusChainListenerRegistryProxy =
65            connect_to_protocol::<focus::FocusChainListenerRegistryMarker>()?;
66        focus_chain_listener_registry
67            .register(focus_chain_listener_client_end)
68            .context("Failed to register focus chain listener.")?;
69
70        Ok(Self::new_listener(
71            text_manager,
72            focus_chain_listener,
73            focus_chain_publisher,
74            metrics_logger,
75        ))
76    }
77
78    /// Creates a new focus listener that holds proxy to text manager.
79    /// The caller is expected to spawn a task to continually listen to focus change event.
80    ///
81    /// # Parameters
82    /// - `text_manager`: A proxy to the text manager service.
83    /// - `focus_chain_listener`: A channel that receives focus chain updates.
84    /// - `focus_chain_publisher`: Forwards focus chain updates to downstream watchers.
85    ///
86    /// # Errors
87    /// If unable to connect to the text_manager protocol.
88    fn new_listener(
89        text_manager: kbd_focus::ControllerProxy,
90        focus_chain_listener: focus::FocusChainListenerRequestStream,
91        focus_chain_publisher: FocusChainProviderPublisher,
92        metrics_logger: metrics::MetricsLogger,
93    ) -> Self {
94        Self { text_manager, focus_chain_listener, focus_chain_publisher, metrics_logger }
95    }
96
97    /// Dispatches focus chain updates from `focus_chain_listener` to `text_manager` and any subscribers of `focus_chain_publisher`.
98    pub async fn dispatch_focus_changes(&mut self) -> Result<(), Error> {
99        while let Some(focus_change) = self.focus_chain_listener.next().await {
100            fuchsia_trace::duration!(c"input", c"dispatch_focus_changes");
101            match focus_change {
102                Ok(focus::FocusChainListenerRequest::OnFocusChange {
103                    focus_chain,
104                    responder,
105                    ..
106                }) => {
107                    fuchsia_trace::duration!(c"input", c"dispatch_focus_changes[processing]");
108                    // Dispatch to downstream watchers.
109                    self.focus_chain_publisher
110                        .set_state_and_notify_if_changed(&focus_chain)
111                        .context("while notifying FocusChainProviderPublisher")?;
112
113                    // Dispatch to text manager.
114                    if let Some(ref focus_chain) = focus_chain.focus_chain {
115                        if let Some(ref view_ref) = focus_chain.last() {
116                            let view_ref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?;
117                            self.text_manager
118                                .notify(view_ref_dup)
119                                .await
120                                .context("while notifying text_manager")?;
121                        }
122                    };
123
124                    responder.send().context("while sending focus chain listener response")?;
125                }
126                Err(e) => self.metrics_logger.log_error(
127                    InputPipelineErrorMetricDimensionEvent::FocusChainListenerRequestError,
128                    std::format!("FocusChainListenerRequest has error: {}.", e),
129                ),
130            }
131        }
132        log::warn!("Stopped dispatching focus changes.");
133        Ok(())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use fidl_fuchsia_ui_focus_ext::FocusChainExt;
141    use fidl_fuchsia_ui_views_ext::ViewRefExt;
142    use futures::join;
143    use pretty_assertions::assert_eq;
144    use {fidl_fuchsia_ui_views as fidl_ui_views, fuchsia_scenic as scenic};
145
146    /// Listens for a ViewRef from a view focus change request on `request_stream`.
147    ///
148    /// # Parameters
149    /// `request_stream`: A channel where ViewFocusChanged requests are received.
150    ///
151    /// # Returns
152    /// The ViewRef of the focused view.
153    async fn expect_focus_ctl_focus_change(
154        mut request_stream: kbd_focus::ControllerRequestStream,
155    ) -> fidl_ui_views::ViewRef {
156        match request_stream.next().await {
157            Some(Ok(kbd_focus::ControllerRequest::Notify { view_ref, responder, .. })) => {
158                let _ = responder.send();
159                view_ref
160            }
161            _ => panic!("Error expecting text_manager focus change."),
162        }
163    }
164
165    async fn expect_focus_koid_chain(
166        focus_chain_provider_proxy: &focus::FocusChainProviderProxy,
167    ) -> focus::FocusKoidChain {
168        focus_chain_provider_proxy
169            .watch_focus_koid_chain(&focus::FocusChainProviderWatchFocusKoidChainRequest::default())
170            .await
171            .expect("watch_focus_koid_chain")
172    }
173
174    /// Tests focused view routing from FocusChainListener to text_manager service.
175    #[fuchsia_async::run_until_stalled(test)]
176    async fn dispatch_focus() -> Result<(), Error> {
177        let (focus_proxy, focus_request_stream) =
178            fidl::endpoints::create_proxy_and_stream::<kbd_focus::ControllerMarker>();
179
180        let (focus_chain_listener_client_end, focus_chain_listener) =
181            fidl::endpoints::create_proxy_and_stream::<focus::FocusChainListenerMarker>();
182
183        let (focus_chain_watcher, focus_chain_provider_stream) =
184            fidl::endpoints::create_proxy_and_stream::<focus::FocusChainProviderMarker>();
185        let (focus_chain_provider_publisher, focus_chain_provider_stream_handler) =
186            focus_chain_provider::make_publisher_and_stream_handler();
187        focus_chain_provider_stream_handler
188            .handle_request_stream(focus_chain_provider_stream)
189            .detach();
190
191        let mut listener = FocusListener::new_listener(
192            focus_proxy,
193            focus_chain_listener,
194            focus_chain_provider_publisher,
195            metrics::MetricsLogger::default(),
196        );
197
198        fuchsia_async::Task::local(async move {
199            let _ = listener.dispatch_focus_changes().await;
200        })
201        .detach();
202
203        // Flush the initial value from the hanging get server.
204        // Note that if the focus chain watcher tried to retrieve the koid chain for the first time
205        // inside the `join!` statement below, concurrently with the update operation, it would end
206        // up receiving the old value.
207        let got_focus_koid_chain = expect_focus_koid_chain(&focus_chain_watcher).await;
208        assert_eq!(got_focus_koid_chain, focus::FocusKoidChain::default());
209
210        let view_ref = scenic::ViewRefPair::new()?.view_ref;
211        let view_ref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?;
212        let focus_chain =
213            focus::FocusChain { focus_chain: Some(vec![view_ref]), ..Default::default() };
214
215        let (_, view_ref, got_focus_koid_chain) = join!(
216            focus_chain_listener_client_end.on_focus_change(focus_chain.duplicate().unwrap()),
217            expect_focus_ctl_focus_change(focus_request_stream),
218            expect_focus_koid_chain(&focus_chain_watcher),
219        );
220
221        assert_eq!(view_ref.get_koid().unwrap(), view_ref_dup.get_koid().unwrap(),);
222        assert!(focus_chain.equivalent(&got_focus_koid_chain).unwrap());
223
224        Ok(())
225    }
226}