focus_chain_provider/
lib.rs

1// Copyright 2022 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
5//! Provides a hanging-get implementation of the `fuchsia.ui.focus.FocusChainProvider` protocol.
6//!
7//! * Create a new publisher and request stream handler using [`make_publisher_and_stream_handler()`].
8//! * Handle a new client's stream of watch requests using
9//!   [`FocusChainProviderRequestStreamHandler::handle_request_stream`].
10//! * Update the focus chain using [`FocusChainProviderPublisher::set_state_and_notify_if_changed`]
11//!   or [`FocusChainProviderPublisher::set_state_and_notify_always`].
12
13mod instance_counter;
14
15use crate::instance_counter::InstanceCounter;
16use async_utils::hanging_get::server as hanging_get;
17use fidl_fuchsia_ui_focus::{
18    self as focus, FocusChainProviderWatchFocusKoidChainResponder, FocusKoidChain,
19};
20use fidl_fuchsia_ui_focus_ext::FocusChainExt;
21use fuchsia_async as fasync;
22use futures::lock::Mutex;
23use futures::stream::TryStreamExt;
24use futures::TryFutureExt;
25use log::error;
26use std::sync::Arc;
27
28// Local shorthand type aliases
29type HangingGetNotifyFn =
30    Box<dyn Fn(&FocusKoidChain, FocusChainProviderWatchFocusKoidChainResponder) -> bool + Send>;
31type HangingGetBroker = hanging_get::HangingGet<
32    FocusKoidChain,
33    FocusChainProviderWatchFocusKoidChainResponder,
34    HangingGetNotifyFn,
35>;
36type HangingGetPublisher = hanging_get::Publisher<
37    FocusKoidChain,
38    FocusChainProviderWatchFocusKoidChainResponder,
39    HangingGetNotifyFn,
40>;
41
42/// Creates a new publisher and stream handler pair. Their initial focus chain value is always
43/// `FocusKoidChain::default()`.
44pub fn make_publisher_and_stream_handler(
45) -> (FocusChainProviderPublisher, FocusChainProviderRequestStreamHandler) {
46    let notify_fn: HangingGetNotifyFn =
47        Box::new(|focus_koid_chain, responder| match responder.send(&focus_koid_chain) {
48            Ok(()) => true,
49            Err(e) => {
50                error!("Failed to send focus chain to client: {e:?}");
51                false
52            }
53        });
54
55    let broker = hanging_get::HangingGet::new(FocusKoidChain::default(), notify_fn);
56    let publisher = broker.new_publisher();
57    let subscriber_counter = InstanceCounter::new();
58
59    (
60        FocusChainProviderPublisher { publisher },
61        FocusChainProviderRequestStreamHandler {
62            broker: Arc::new(Mutex::new(broker)),
63            subscriber_counter,
64        },
65    )
66}
67
68/// Allows new focus chain values to be stored for transmission to watcher clients (through the
69/// corresponding [`FocusChainProviderRequestStreamHandler`]).
70///
71/// Instantiate using [`make_publisher_and_stream_handler()`].
72#[derive(Clone)]
73pub struct FocusChainProviderPublisher {
74    publisher: HangingGetPublisher,
75}
76
77impl FocusChainProviderPublisher {
78    /// Updates the focus chain. If the new value is different from the previous value, sends an
79    /// update to all listeners.
80    ///
81    /// Returns an error if there are any problems with duplicating the `FocusChain`.
82    pub fn set_state_and_notify_if_changed<C: FocusChainExt>(
83        &self,
84        new_state: &C,
85    ) -> Result<(), zx::Status> {
86        let new_state = new_state.to_focus_koid_chain()?;
87        let publisher = self.publisher.clone();
88        publisher.update(|old_state| match old_state.as_ref().unwrap().equivalent(&new_state) {
89            Ok(true) => false,
90            Ok(false) => {
91                *old_state = Some(new_state);
92                true
93            }
94            Err(e) => unreachable!("Unexpected state {e:?}"),
95        });
96        Ok(())
97    }
98
99    /// Updates the focus chain. Sends an update to all listeners even if the value hasn't changed.
100    ///
101    /// Returns an error if there are any problems with duplicating the `FocusChain`.
102    pub fn set_state_and_notify_always<C: FocusChainExt>(
103        &self,
104        new_state: &C,
105    ) -> Result<(), zx::Status> {
106        let publisher = self.publisher.clone();
107        publisher.set(new_state.to_focus_koid_chain()?);
108        Ok(())
109    }
110}
111
112/// Handles streams of requests from `FocusChainProvider` clients, responding to them with the
113/// latest value from the corresponding [`FocusChainProviderPublisher`].
114///
115/// Instantiate using [`make_publisher_and_stream_handler()`].
116#[derive(Clone)]
117pub struct FocusChainProviderRequestStreamHandler {
118    broker: Arc<Mutex<HangingGetBroker>>,
119    subscriber_counter: InstanceCounter,
120}
121
122impl FocusChainProviderRequestStreamHandler {
123    /// Handles a [`fidl_fuchsia_ui_focus::FocusChainProviderRequestStream`] for a single client,
124    /// spawning a new local `Task`.
125    #[must_use = "The Task must be retained or `.detach()`ed."]
126    pub fn handle_request_stream(
127        &self,
128        mut stream: focus::FocusChainProviderRequestStream,
129    ) -> fasync::Task<()> {
130        let broker = self.broker.clone();
131        let counter = self.subscriber_counter.clone();
132        fasync::Task::local(
133            async move {
134                let subscriber = broker.lock().await.new_subscriber();
135                // Will be dropped when the task is being dropped.
136                let _count_token = counter.make_token();
137                while let Some(req) = stream.try_next().await? {
138                    match req {
139                        focus::FocusChainProviderRequest::WatchFocusKoidChain {
140                            payload: _payload,
141                            responder,
142                        } => {
143                            subscriber.register(responder)?;
144                        }
145                    }
146                }
147                Ok(())
148            }
149            .unwrap_or_else(|e: anyhow::Error| error!("{e:#?}")),
150        )
151    }
152
153    /// Returns the number of active subscribers. Mostly useful for tests.
154    pub fn subscriber_count(&self) -> usize {
155        self.subscriber_counter.count()
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use fidl_fuchsia_ui_focus_test_helpers::make_focus_chain;
163
164    // Most of the testing happens in `async_utils::hanging_get`.
165    #[fuchsia::test]
166    async fn smoke_test() {
167        let (publisher, stream_handler) = super::make_publisher_and_stream_handler();
168        let (client, stream) =
169            fidl::endpoints::create_proxy_and_stream::<focus::FocusChainProviderMarker>();
170        stream_handler.handle_request_stream(stream).detach();
171        assert_eq!(stream_handler.subscriber_count(), 0);
172
173        let received_focus_koid_chain = client
174            .watch_focus_koid_chain(&focus::FocusChainProviderWatchFocusKoidChainRequest::default())
175            .await
176            .expect("watch_focus_koid_chain");
177        assert!(received_focus_koid_chain.equivalent(&FocusKoidChain::default()).unwrap());
178        assert_eq!(stream_handler.subscriber_count(), 1);
179
180        let (served_focus_chain, _view_ref_controls) = make_focus_chain(2);
181        publisher.set_state_and_notify_if_changed(&served_focus_chain).expect("set_state");
182        let received_focus_koid_chain = client
183            .watch_focus_koid_chain(&focus::FocusChainProviderWatchFocusKoidChainRequest::default())
184            .await
185            .expect("watch_focus_chain");
186        assert!(received_focus_koid_chain.equivalent(&served_focus_chain).unwrap());
187        assert_eq!(stream_handler.subscriber_count(), 1);
188    }
189
190    #[fuchsia::test]
191    async fn only_newest_value_is_sent() {
192        let (publisher, stream_handler) = super::make_publisher_and_stream_handler();
193        let (client, stream) =
194            fidl::endpoints::create_proxy_and_stream::<focus::FocusChainProviderMarker>();
195        stream_handler.handle_request_stream(stream).detach();
196
197        let received_focus_koid_chain = client
198            .watch_focus_koid_chain(&focus::FocusChainProviderWatchFocusKoidChainRequest::default())
199            .await
200            .expect("watch_focus_koid_chain");
201        assert!(received_focus_koid_chain.equivalent(&FocusKoidChain::default()).unwrap());
202
203        let (served_focus_chain, _view_ref_controls) = make_focus_chain(2);
204        publisher.set_state_and_notify_if_changed(&served_focus_chain).expect("set_state");
205
206        let (served_focus_chain, _view_ref_controls) = make_focus_chain(3);
207        publisher.set_state_and_notify_if_changed(&served_focus_chain).expect("set_state");
208
209        let received_focus_koid_chain = client
210            .watch_focus_koid_chain(&focus::FocusChainProviderWatchFocusKoidChainRequest::default())
211            .await
212            .expect("watch_focus_chain");
213        assert_eq!(received_focus_koid_chain.len(), 3);
214        assert!(received_focus_koid_chain.equivalent(&served_focus_chain).unwrap());
215    }
216}