blob: 0a3cf66ec090c672137af4f92d7730695a5c79d7 [file] [log] [blame]
// Copyright 2021 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 {
anyhow::Error,
fidl::prelude::*,
fidl_fuchsia_accessibility::{MagnificationHandlerMarker, MagnifierMarker},
fidl_fuchsia_accessibility_scene as a11y_view,
fidl_fuchsia_input_injection::InputDeviceRegistryRequestStream,
fidl_fuchsia_session_scene::{
ManagerRequest as SceneManagerRequest, ManagerRequestStream as SceneManagerRequestStream,
},
fidl_fuchsia_ui_accessibility_view::{
RegistryRequest as A11yViewRegistryRequest,
RegistryRequestStream as A11yViewRegistryRequestStream,
},
fidl_fuchsia_ui_app as ui_app, fidl_fuchsia_ui_composition as fland,
fidl_fuchsia_ui_scenic::ScenicMarker,
fidl_fuchsia_ui_views as ui_views, fuchsia_async as fasync,
fuchsia_component::{client::connect_to_protocol, server::ServiceFs},
fuchsia_inspect as inspect,
fuchsia_syslog::{fx_log_err, fx_log_info, fx_log_warn},
fuchsia_zircon as zx,
futures::lock::Mutex,
futures::{StreamExt, TryStreamExt},
scene_management::{self, SceneManager},
std::rc::Rc,
std::sync::Arc,
};
mod input_device_registry_server;
mod input_pipeline;
enum ExposedServices {
AccessibilityViewRegistry(A11yViewRegistryRequestStream),
SceneManager(SceneManagerRequestStream),
InputDeviceRegistry(InputDeviceRegistryRequestStream),
}
#[fuchsia::main(logging_tags = [ "scene_manager" ])]
async fn main() -> Result<(), Error> {
let mut fs = ServiceFs::new_local();
inspect_runtime::serve(inspect::component::inspector(), &mut fs)?;
fs.dir("svc").add_fidl_service(ExposedServices::AccessibilityViewRegistry);
fs.dir("svc").add_fidl_service(ExposedServices::SceneManager);
fs.dir("svc").add_fidl_service(ExposedServices::InputDeviceRegistry);
fs.take_and_serve_directory_handle()?;
let (input_device_registry_server, input_device_registry_request_stream_receiver) =
input_device_registry_server::make_server_and_receiver();
// This call should normally never fail. The ICU data loader must be kept alive to ensure
// Unicode data is kept in memory.
let icu_data_loader = icu_data::Loader::new().unwrap();
let scenic = connect_to_protocol::<ScenicMarker>()?;
let use_flatland = scenic.uses_flatland().await.expect("Failed to get flatland info.");
let display_ownership =
scenic.get_display_ownership_event().await.expect("Failed to get display ownership.");
fx_log_info!("Instantiating SceneManager, use_flatland: {:?}", use_flatland);
let scene_manager: Arc<Mutex<Box<dyn SceneManager>>> = if use_flatland {
// TODO(fxbug.dev/86379): Support for insertion of accessibility view. Pass ViewRefInstalled
// to the SceneManager, the same way we do for the Gfx branch.
let display = connect_to_protocol::<fland::FlatlandDisplayMarker>()?;
let root_flatland = connect_to_protocol::<fland::FlatlandMarker>()?;
let pointerinjector_flatland = connect_to_protocol::<fland::FlatlandMarker>()?;
let a11y_flatland = connect_to_protocol::<fland::FlatlandMarker>()?;
let cursor_view_provider = connect_to_protocol::<ui_app::ViewProviderMarker>()?;
let a11y_view_provider = connect_to_protocol::<a11y_view::ProviderMarker>()?;
Arc::new(Mutex::new(Box::new(
scene_management::FlatlandSceneManager::new(
scenic,
display,
pointerinjector_flatland,
root_flatland,
a11y_flatland,
cursor_view_provider,
a11y_view_provider,
)
.await?,
)))
} else {
let view_ref_installed = connect_to_protocol::<ui_views::ViewRefInstalledMarker>()?;
let gfx_scene_manager: Arc<Mutex<Box<dyn SceneManager>>> = Arc::new(Mutex::new(Box::new(
scene_management::GfxSceneManager::new(scenic, view_ref_installed, None, None).await?,
)));
if let Err(e) = register_gfx_as_magnifier(Arc::clone(&gfx_scene_manager)) {
fx_log_warn!("failed to register as the magnification handler: {:?}", e);
}
gfx_scene_manager
};
// Create a node under root to hang all input pipeline inspect data off of.
let inspect_node =
Rc::new(inspect::component::inspector().root().create_child("input_pipeline"));
// Start input pipeline.
if let Ok(input_pipeline) = input_pipeline::handle_input(
use_flatland,
scene_manager.clone(),
input_device_registry_request_stream_receiver,
icu_data_loader,
&inspect_node.clone(),
display_ownership,
)
.await
{
fasync::Task::local(input_pipeline.handle_input_events()).detach();
}
while let Some(service_request) = fs.next().await {
match service_request {
ExposedServices::AccessibilityViewRegistry(request_stream) => {
fasync::Task::local(handle_accessibility_view_registry_request_stream(
request_stream,
Arc::clone(&scene_manager),
))
.detach()
}
ExposedServices::SceneManager(request_stream) => {
fasync::Task::local(handle_scene_manager_request_stream(
request_stream,
Arc::clone(&scene_manager),
))
.detach();
}
ExposedServices::InputDeviceRegistry(request_stream) => {
match &input_device_registry_server.handle_request(request_stream).await {
Ok(()) => (),
Err(e) => {
// If `handle_request()` returns `Err`, then the `unbounded_send()` call
// from `handle_request()` failed with either:
// * `TrySendError::SendErrorKind::Full`, or
// * `TrySendError::SendErrorKind::Disconnected`.
//
// These are unexpected, because:
// * `Full` can't happen, because `InputDeviceRegistryServer`
// uses an `UnboundedSender`.
// * `Disconnected` is highly unlikely, because the corresponding
// `UnboundedReceiver` lives in `main::input_fut`, and `input_fut`'s
// lifetime is nearly as long as `input_device_registry_server`'s.
//
// Nonetheless, InputDeviceRegistry isn't critical to production use.
// So we just log the error and move on.
fx_log_warn!(
"failed to forward InputDeviceRegistryRequestStream: {:?}; \
must restart to enable input injection",
e
)
}
}
}
}
}
fx_log_info!("Finished service handler loop; exiting main.");
Ok(())
}
pub async fn handle_scene_manager_request_stream(
mut request_stream: SceneManagerRequestStream,
scene_manager: Arc<Mutex<Box<dyn scene_management::SceneManager>>>,
) {
while let Ok(Some(request)) = request_stream.try_next().await {
match request {
SceneManagerRequest::SetRootView { view_provider, responder } => {
if let Ok(proxy) = view_provider.into_proxy() {
let mut scene_manager = scene_manager.lock().await;
match scene_manager.set_root_view(proxy).await {
Ok(mut view_ref) => match responder.send(&mut view_ref) {
Ok(_) => {}
Err(e) => {
fx_log_err!("Error responding to SetRootView(): {}", e);
}
},
Err(e) => {
// Log an error and close the connection. This can be a consequence of
// the child View not connecting to the scene graph (hence we don't
// receive the ViewRef to return), or perhaps an internal bug which
// requires further investigation.
fx_log_err!("Failed to obtain ViewRef from set_root_view(): {}", e);
}
}
}
}
};
}
}
pub async fn handle_accessibility_view_registry_request_stream(
mut request_stream: A11yViewRegistryRequestStream,
scene_manager: Arc<Mutex<Box<dyn scene_management::SceneManager>>>,
) {
while let Ok(Some(request)) = request_stream.try_next().await {
match request {
A11yViewRegistryRequest::CreateAccessibilityViewHolder {
a11y_view_ref: _,
a11y_view_token,
responder,
..
} => {
let mut scene_manager = scene_manager.lock().await;
let r = scene_manager.insert_a11y_view(a11y_view_token);
let _ = match r {
Ok(mut result) => {
let _ = responder.send(&mut result);
}
Err(e) => {
fx_log_warn!("Closing A11yViewRegistry connection due to error {:?}", e);
responder.control_handle().shutdown_with_epitaph(zx::Status::PEER_CLOSED);
}
};
}
A11yViewRegistryRequest::CreateAccessibilityViewport {
viewport_creation_token: _,
responder,
..
} => {
fx_log_err!("A11yViewRegistry.CreateAccessibilityViewport not implemented!");
responder.control_handle().shutdown_with_epitaph(zx::Status::PEER_CLOSED);
}
};
}
}
fn register_gfx_as_magnifier(
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
) -> Result<(), anyhow::Error> {
let (magnification_handler_client, magnification_handler_server) =
fidl::endpoints::create_request_stream::<MagnificationHandlerMarker>()?;
scene_management::GfxSceneManager::handle_magnification_handler_request_stream(
magnification_handler_server,
scene_manager,
);
let magnifier_proxy = connect_to_protocol::<MagnifierMarker>()?;
magnifier_proxy.register_handler(magnification_handler_client)?;
Ok(())
}
#[cfg(test)]
mod tests {
use anyhow::format_err;
use fidl::endpoints::create_endpoints;
use fidl_fuchsia_session_scene::{
ManagerMarker as SceneManagerMarker, ManagerProxy as SceneManagerProxy,
};
use fidl_fuchsia_ui_accessibility_view::{RegistryMarker, RegistryProxy};
use fidl_fuchsia_ui_app::{ViewProviderMarker, ViewProviderRequest};
use fidl_fuchsia_ui_scenic as ui_scenic;
use fidl_fuchsia_ui_views as ui_views;
use fidl_fuchsia_ui_views::ViewToken;
use fuchsia_async as fasync;
use fuchsia_component_test::{
Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route,
};
use fuchsia_scenic as scenic;
use futures::TryStreamExt;
const SCENIC_URL: &str = "#meta/scenic.cm";
const MOCK_COBALT_URL: &str = "#meta/mock_cobalt.cm";
const SCENE_MANAGER_URL: &str = "#meta/scene_manager.cm";
const FAKE_HDCP_URL: &str = "#meta/hdcp.cm";
async fn setup_realm() -> Result<RealmInstance, anyhow::Error> {
let builder = RealmBuilder::new().await?;
let mock_cobalt =
builder.add_child("mock_cobalt", MOCK_COBALT_URL, ChildOptions::new().eager()).await?;
let hdcp = builder.add_child("hdcp", FAKE_HDCP_URL, ChildOptions::new().eager()).await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.sysmem.Allocator"))
.capability(Capability::protocol_by_name("fuchsia.tracing.provider.Registry"))
.from(Ref::parent())
.to(&hdcp),
)
.await?;
let scenic = builder
.add_child("scenic", SCENIC_URL, ChildOptions::new().eager())
.await
.expect("Failed to start scenic");
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.hardware.display.Provider"))
.from(&hdcp)
.to(&scenic),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.scheduler.ProfileProvider"))
.capability(Capability::protocol_by_name("fuchsia.sysmem.Allocator"))
.capability(Capability::protocol_by_name("fuchsia.tracing.provider.Registry"))
.capability(Capability::protocol_by_name("fuchsia.ui.input.ImeService"))
.capability(Capability::protocol_by_name("fuchsia.vulkan.loader.Loader"))
.from(Ref::parent())
.to(&scenic),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.cobalt.LoggerFactory"))
.from(&mock_cobalt)
.to(&scenic),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.ui.views.ViewRefFocused"))
.from(&scenic)
.to(Ref::parent()),
)
.await?;
let scene_manager = builder
.add_child("scene_manager", SCENE_MANAGER_URL, ChildOptions::new().eager())
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name(
"fuchsia.ui.accessibility.view.Registry",
))
.capability(Capability::protocol_by_name("fuchsia.session.scene.Manager"))
.from(&scene_manager)
.to(Ref::parent()),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.ui.app.ViewProvider"))
.from(Ref::parent())
.to(&scene_manager),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.ui.views.ViewRefInstalled"))
.capability(Capability::protocol_by_name("fuchsia.ui.scenic.Scenic"))
.capability(Capability::protocol_by_name("fuchsia.ui.scenic.Session"))
.capability(Capability::protocol_by_name("fuchsia.ui.composition.Flatland"))
.capability(Capability::protocol_by_name(
"fuchsia.ui.composition.FlatlandDisplay",
))
.from(&scenic)
.to(Ref::parent())
.to(&scene_manager),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.from(Ref::parent())
.to(&hdcp)
.to(&mock_cobalt)
.to(&scene_manager)
.to(&scenic),
)
.await?;
let instance = builder.build().await.expect("Failed to build instance");
Ok(instance)
}
async fn present_session_changes(session: &scenic::SessionPtr) {
let _ = session
.lock()
// Passing 0 for requested_presentation_time tells scenic that that it should process
// enqueued operation as soon as possible.
// Passing 0 for requested_prediction_span guarantees that scenic will provide at least
// one future time.
.present2(0, 0)
.await
.expect("Failed to present");
}
async fn view_is_focused(
view_ref_focused_proxy: &ui_views::ViewRefFocusedProxy,
) -> Result<bool, anyhow::Error> {
view_ref_focused_proxy
.watch()
.await
.map_err(|err| format_err!("ViewRefFocused fidl error for client view: {:?}", err))
.map(|focused_state| focused_state.focused.unwrap_or(false))
}
async fn insert_a11y_view(
session: scenic::SessionPtr,
accessibility_view_registry_proxy: &RegistryProxy,
) -> Result<(scenic::View, scenic::ViewHolder, ui_views::ViewRef), anyhow::Error> {
// Create a11y ViewRef and ViewToken pairs.
let mut a11y_view_token_pair = scenic::ViewTokenPair::new()?;
let a11y_viewref_pair = scenic::ViewRefPair::new()?;
let mut viewref_dup_for_registry =
fuchsia_scenic::duplicate_view_ref(&a11y_viewref_pair.view_ref)?;
let viewref_dup_to_return =
fuchsia_scenic::duplicate_view_ref(&a11y_viewref_pair.view_ref)?;
// Create a11y view and try inserting into scene.
let a11y_view = scenic::View::new3(
session.clone(),
a11y_view_token_pair.view_token,
a11y_viewref_pair.control_ref,
a11y_viewref_pair.view_ref,
Some(String::from("a11y view")),
);
present_session_changes(&session).await;
let proxy_view_holder_token = accessibility_view_registry_proxy
.create_accessibility_view_holder(
&mut viewref_dup_for_registry,
&mut a11y_view_token_pair.view_holder_token,
)
.await
.unwrap();
// Attach proxy view holder to the scene. This step is necessary to ensure that the client
// view is attached to the scene.
let proxy_view_holder = scenic::ViewHolder::new(
session.clone(),
proxy_view_holder_token,
Some(String::from("proxy view holder")),
);
a11y_view.add_child(&proxy_view_holder);
present_session_changes(&session).await;
Ok((a11y_view, proxy_view_holder, viewref_dup_to_return))
}
async fn set_root_view(
client_session: scenic::SessionPtr,
scene_manager: SceneManagerProxy,
) -> Result<(scenic::View, ui_views::ViewRef), anyhow::Error> {
// Create ViewProvider endpoints. The scene manager uses this service to create the root
// view.
let (view_provider_client, view_provider_server) = create_endpoints::<ViewProviderMarker>()
.expect("Failed to create local ViewProvider proxy and stream");
let client_view_ref = scene_manager.set_root_view(view_provider_client).await.unwrap();
// Create a mock ViewProvider.
let mut view_provider_stream = view_provider_server.into_stream()?;
let client_view = match view_provider_stream.try_next().await.unwrap() {
Some(ViewProviderRequest::CreateViewWithViewRef {
token,
view_ref_control,
view_ref,
..
}) => {
let view_token = ViewToken { value: token };
Ok(scenic::View::new3(
client_session.clone(),
view_token,
view_ref_control,
view_ref,
Some(String::from("root view")),
))
}
_ => Err("Unexpected method call"),
}
.expect("CreateViewWithViewRef was not called");
present_session_changes(&client_session).await;
Ok((client_view, client_view_ref))
}
#[allow(unused_variables)]
#[fasync::run_singlethreaded(test)]
async fn test_attach_a11y_view() -> Result<(), anyhow::Error> {
// Setup realm instance.
let realm_instance = setup_realm().await.expect("Failed to setup realm");
// Connect to services.
let accessibility_view_registry_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<RegistryMarker>()?;
let view_ref_installed_proxy = realm_instance
.root
.connect_to_protocol_at_exposed_dir::<ui_views::ViewRefInstalledMarker>()?;
let scenic_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<ui_scenic::ScenicMarker>()?;
// Instantiate scenic session in which to create a11y view.
let (session_proxy, session_request_stream) = fidl::endpoints::create_proxy()?;
scenic_proxy.create_session2(session_request_stream, None, None)?;
let session = scenic::Session::new(session_proxy);
// Create a11y view. Proxy view holder is not required for this test case, and can be dropped.
let (a11y_view, _, mut a11y_view_ref) =
insert_a11y_view(session, &accessibility_view_registry_proxy).await.unwrap();
// Listen for a11y view ref installed event.
let watch_result = view_ref_installed_proxy
.watch(&mut a11y_view_ref)
.await
.expect("ViewRefInstalled fidl error for a11y view")
.expect("ViewRefInstalledError for a11y view");
Ok(())
}
#[allow(unused_variables)]
#[fasync::run_singlethreaded(test)]
async fn test_set_root_view_before_a11y_view() -> Result<(), anyhow::Error> {
// Setup realm instance.
let realm_instance = setup_realm().await.expect("Failed to setup realm");
// Connect to services.
let accessibility_view_registry_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<RegistryMarker>()?;
let view_ref_installed_proxy = realm_instance
.root
.connect_to_protocol_at_exposed_dir::<ui_views::ViewRefInstalledMarker>()?;
let scenic_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<ui_scenic::ScenicMarker>()?;
let scene_manager =
realm_instance.root.connect_to_protocol_at_exposed_dir::<SceneManagerMarker>().unwrap();
// Instantiate scenic session in which to create a11y view.
let (session_proxy, session_request_stream) = fidl::endpoints::create_proxy()?;
scenic_proxy.create_session2(session_request_stream, None, None)?;
let session = scenic::Session::new(session_proxy);
// Insert a11y view. The a11y view ref is not required for this test case, and can be
// dropped.
let (a11y_view, proxy_view_holder, _) =
insert_a11y_view(session, &accessibility_view_registry_proxy).await.unwrap();
// Create session in which to create root view.
let (client_session_proxy, client_session_request_stream) =
fidl::endpoints::create_proxy()?;
scenic_proxy.create_session2(client_session_request_stream, None, None)?;
let client_session = scenic::Session::new(client_session_proxy);
// Set root view.
let (client_view, mut client_view_ref) =
set_root_view(client_session, scene_manager).await.unwrap();
// Verify that the client view was attached to the scene.
let watch_result = view_ref_installed_proxy
.watch(&mut client_view_ref)
.await
.expect("ViewRefInstalled fidl error for client view")
.expect("ViewRefInstalledError for client view");
Ok(())
}
#[allow(unused_variables)]
#[fasync::run_singlethreaded(test)]
async fn test_set_root_view_after_a11y_view() -> Result<(), anyhow::Error> {
// Setup realm instance.
let realm_instance = setup_realm().await.expect("Failed to setup realm");
// Connect to services.
let accessibility_view_registry_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<RegistryMarker>()?;
let view_ref_installed_proxy = realm_instance
.root
.connect_to_protocol_at_exposed_dir::<ui_views::ViewRefInstalledMarker>()?;
let scenic_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<ui_scenic::ScenicMarker>()?;
let scene_manager =
realm_instance.root.connect_to_protocol_at_exposed_dir::<SceneManagerMarker>().unwrap();
// Create session in which to create root view.
// The |view_ref_focused| endpoint is required to verify that the client view is refocused
// after the a11y view is inserted.
let (client_session_proxy, client_session_request_stream) =
fidl::endpoints::create_proxy()?;
let (view_ref_focused_proxy, view_ref_focused_request_stream) =
fidl::endpoints::create_proxy()?;
let mut session_endpoints = ui_scenic::SessionEndpoints::EMPTY;
session_endpoints.session = Some(client_session_request_stream);
session_endpoints.view_ref_focused = Some(view_ref_focused_request_stream);
scenic_proxy.create_session_t(session_endpoints).await?;
let client_session = scenic::Session::new(client_session_proxy);
// Create ViewProvider endpoints. The scene manager uses this service to create the root
// view.
let (view_provider_client, view_provider_server) = create_endpoints::<ViewProviderMarker>()
.expect("Failed to create local ViewProvider proxy and stream");
// Set root view. Client view ref is not required for this test case, and can be dropped.
let (client_view, _) = set_root_view(client_session, scene_manager).await.unwrap();
// Instantiate scenic session in which to create a11y view.
let (session_proxy, session_request_stream) = fidl::endpoints::create_proxy()?;
scenic_proxy.create_session2(session_request_stream, None, None)?;
let session = scenic::Session::new(session_proxy);
// Insert a11y view. The a11y view ref is not required for this test case, and can be
// dropped.
let (a11y_view, proxy_view_holder, _) =
insert_a11y_view(session, &accessibility_view_registry_proxy).await.unwrap();
// Verify that the client view was focused.
let client_view_focused = view_is_focused(&view_ref_focused_proxy).await.unwrap();
if !client_view_focused {
panic!("Client view ref not focused after a11y view inserted");
}
Ok(())
}
}