blob: 30b740c947b6ee1aef0c62a96428cbf967ba4c3f [file] [log] [blame]
// 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(())
}
}