| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use fidl_fuchsia_ui_keyboard_focus as fidl_focus; |
| use { |
| anyhow::{format_err, Context, Error}, |
| fidl_fuchsia_ui_focus as focus, fidl_fuchsia_ui_shortcut as fidl_ui_shortcut, |
| fuchsia_component::client::connect_to_service, |
| fuchsia_syslog::fx_log_err, |
| futures::StreamExt, |
| }; |
| |
| /// Registers as a focus chain listener and dispatches focus chain updates to IME |
| /// and shortcut manager. |
| pub async fn handle_focus_changes() -> Result<(), Error> { |
| let ime = connect_to_service::<fidl_focus::ControllerMarker>()?; |
| let shortcut_manager = connect_to_service::<fidl_ui_shortcut::ManagerMarker>()?; |
| |
| let (focus_chain_listener_client_end, focus_chain_listener) = |
| fidl::endpoints::create_request_stream::<focus::FocusChainListenerMarker>()?; |
| |
| let focus_chain_listener_registry: focus::FocusChainListenerRegistryProxy = |
| connect_to_service::<focus::FocusChainListenerRegistryMarker>()?; |
| focus_chain_listener_registry |
| .register(focus_chain_listener_client_end) |
| .context("Failed to register focus chain listener.")?; |
| |
| dispatch_focus_changes(ime, shortcut_manager, focus_chain_listener).await |
| } |
| |
| /// Dispatches focus chain updates from `focus_chain_listener` to |
| /// `focus_ctl` and `shortcut_manager`. |
| /// |
| /// # Parameters |
| /// `focus_ctl`: A proxy to the focus controller. |
| /// `shortcut_manager`: A proxy to the shortcut manager service. |
| /// `focus_chain_listener`: A channel that receives focus chain updates. |
| async fn dispatch_focus_changes( |
| focus_ctl: fidl_focus::ControllerProxy, |
| shortcut_manager: fidl_ui_shortcut::ManagerProxy, |
| mut focus_chain_listener: focus::FocusChainListenerRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(focus_change) = focus_chain_listener.next().await { |
| match focus_change { |
| Ok(focus::FocusChainListenerRequest::OnFocusChange { |
| focus_chain, responder, .. |
| }) => { |
| // Dispatch to IME. |
| if let Some(ref focus_chain) = focus_chain.focus_chain { |
| if let Some(ref view_ref) = focus_chain.last() { |
| let mut view_ref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?; |
| focus_ctl.notify(&mut view_ref_dup).await?; |
| } |
| }; |
| |
| // Dispatch to shortcut manager. |
| shortcut_manager.handle_focus_change(focus_chain).await?; |
| |
| responder.send()?; |
| } |
| Err(e) => fx_log_err!("FocusChainListenerRequest has error: {}.", e), |
| } |
| } |
| |
| Err(format_err!("Stopped dispatching focus changes.")) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, fidl_fuchsia_ui_focus as fidl_ui_focus, |
| fidl_fuchsia_ui_shortcut as fidl_ui_shortcut, fidl_fuchsia_ui_views as fidl_ui_views, |
| fuchsia_scenic as scenic, fuchsia_zircon::AsHandleRef, futures::join, |
| }; |
| |
| /// Listens for a ViewRef from a view focus change request on `request_stream`. |
| /// |
| /// # Parameters |
| /// `request_stream`: A channel where ViewFocusChanged requests are received. |
| /// |
| /// # Returns |
| /// The ViewRef of the focused view. |
| async fn expect_focus_ctl_focus_change( |
| mut request_stream: fidl_focus::ControllerRequestStream, |
| ) -> fidl_ui_views::ViewRef { |
| match request_stream.next().await { |
| Some(Ok(fidl_focus::ControllerRequest::Notify { view_ref, responder, .. })) => { |
| let _ = responder.send(); |
| view_ref |
| } |
| _ => panic!("Error expecting IME focus change."), |
| } |
| } |
| |
| /// Listens for a ViewRef from a view focus change request on `manager_request_stream`. |
| /// |
| /// # Parameters |
| /// `shortcut_manager_request_stream`: A stream of Manager requests that contains |
| /// HandleFocusChange requests. |
| /// |
| /// # Returns |
| /// The updated FocusChain. |
| async fn expect_shortcut_focus_change( |
| mut shortcut_manager_request_stream: fidl_ui_shortcut::ManagerRequestStream, |
| ) -> fidl_ui_focus::FocusChain { |
| match shortcut_manager_request_stream.next().await { |
| Some(Ok(fidl_ui_shortcut::ManagerRequest::HandleFocusChange { |
| focus_chain, |
| responder, |
| .. |
| })) => { |
| let _ = responder.send(); |
| focus_chain |
| } |
| _ => panic!("Error expecting shortcut focus change."), |
| } |
| } |
| |
| /// Tests focused view routing from FocusChainListener to IME service and shortcut manager. |
| #[fuchsia_async::run_until_stalled(test)] |
| async fn dispatch_focus() -> Result<(), Error> { |
| let (focus_proxy, focus_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_focus::ControllerMarker>()?; |
| let (shortcut_manager_proxy, shortcut_manager_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_ui_shortcut::ManagerMarker>()?; |
| |
| let (focus_chain_listener_client_end, focus_chain_listener) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_ui_focus::FocusChainListenerMarker>()?; |
| |
| fuchsia_async::Task::spawn(async move { |
| let _ = |
| dispatch_focus_changes(focus_proxy, shortcut_manager_proxy, focus_chain_listener) |
| .await; |
| }) |
| .detach(); |
| |
| let view_ref = scenic::ViewRefPair::new()?.view_ref; |
| let view_ref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?; |
| let focus_chain = fidl_ui_focus::FocusChain { |
| focus_chain: Some(vec![view_ref]), |
| ..fidl_ui_focus::FocusChain::EMPTY |
| }; |
| |
| let (_, view_ref, got_focus_chain) = join!( |
| focus_chain_listener_client_end.on_focus_change(focus_chain), |
| expect_focus_ctl_focus_change(focus_request_stream), |
| expect_shortcut_focus_change(shortcut_manager_request_stream), |
| ); |
| |
| assert_eq!( |
| view_ref.reference.as_handle_ref().get_koid(), |
| view_ref_dup.reference.as_handle_ref().get_koid() |
| ); |
| |
| let got_focus_chain_vec = got_focus_chain.focus_chain.unwrap(); |
| assert_eq!(1, got_focus_chain_vec.len()); |
| assert_eq!( |
| view_ref_dup.reference.as_handle_ref().get_koid(), |
| got_focus_chain_vec.first().unwrap().reference.as_handle_ref().get_koid() |
| ); |
| |
| Ok(()) |
| } |
| } |