Skip to main content

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