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