blob: e2a1ec2b30ea737b71c7f3c831b5517a5b337a29 [file] [log] [blame]
// Copyright 2015 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.
#include "src/ui/bin/root_presenter/presentation.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include <cmath>
#include <src/lib/fostr/fidl/fuchsia/ui/input/formatting.h>
#include "src/ui/bin/root_presenter/displays/display_configuration.h"
#include "src/ui/bin/root_presenter/inspect.h"
#include "src/ui/bin/root_presenter/safe_presenter.h"
#include <glm/glm.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wextra-semi"
#include <glm/ext.hpp>
#pragma GCC diagnostic pop
namespace root_presenter {
namespace {
// TODO(fxbug.dev/24474): Don't hardcode Z bounds in multiple locations.
constexpr float kDefaultRootViewDepth = 1000;
void ChattyReportLog(const fuchsia::ui::input::InputReport& report) {
static uint32_t chatty = 0;
if (chatty++ < ChattyMax()) {
FX_LOGS(INFO) << "RP-PtrReport[" << chatty << "/" << ChattyMax() << "]: " << report;
}
}
void ChattyEventLog(const fuchsia::ui::input::InputEvent& event) {
static uint32_t chatty = 0;
if (chatty++ < ChattyMax()) {
FX_LOGS(INFO) << "RP-PtrEvent[" << chatty << "/" << ChattyMax() << "]: " << event;
}
}
} // namespace
Presentation::Presentation(inspect::Node inspect_node, sys::ComponentContext* component_context,
fuchsia::ui::scenic::Scenic* scenic,
std::unique_ptr<scenic::Session> session,
fuchsia::ui::views::FocuserPtr focuser,
int32_t display_startup_rotation_adjustment)
: inspect_node_(std::move(inspect_node)),
input_report_inspector_(inspect_node_.CreateChild("input_reports")),
input_event_inspector_(inspect_node_.CreateChild("input_events")),
root_session_(std::move(session)),
compositor_(root_session_.get()),
layer_stack_(root_session_.get()),
layer_(root_session_.get()),
renderer_(root_session_.get()),
scene_(root_session_.get()),
camera_(scene_),
injector_session_(scenic),
proxy_session_(scenic),
display_startup_rotation_adjustment_(display_startup_rotation_adjustment),
presentation_binding_(this),
a11y_binding_(this),
a11y_view_registry_binding_(this),
safe_presenter_root_(root_session_.get()),
safe_presenter_injector_(&injector_session_),
safe_presenter_proxy_(&proxy_session_),
view_focuser_(std::move(focuser)),
color_transform_handler_(component_context, compositor_.id(), root_session_.get(),
&safe_presenter_root_) {
FX_DCHECK(component_context);
component_context->outgoing()->AddPublicService(presenter_bindings_.GetHandler(this));
component_context->outgoing()->AddPublicService<fuchsia::ui::accessibility::view::Registry>(
[this](fidl::InterfaceRequest<fuchsia::ui::accessibility::view::Registry> request) {
if (a11y_view_registry_binding_.is_bound()) {
FX_LOGS(ERROR) << "Replacing a11y binding";
a11y_view_registry_binding_.Unbind();
}
a11y_view_registry_binding_.Bind(std::move(request));
});
compositor_.SetLayerStack(layer_stack_);
layer_stack_.AddLayer(layer_);
renderer_.SetCamera(camera_);
layer_.SetRenderer(renderer_);
// Create the root view's scene.
// TODO(fxbug.dev/24456): we add a directional light and a point light, expecting
// only one of them to be active at a time. This logic is implicit in
// EngineRenderer, since no shadow-mode supports both directional and point
// lights (either one or the other). When directional light support is added
// to PaperRenderer, the code here will result in over-brightening, and will
// need to be adjusted at that time.
scenic::AmbientLight ambient_light(root_session_.get());
scenic::DirectionalLight directional_light(root_session_.get());
scenic::PointLight point_light(root_session_.get());
scene_.AddLight(ambient_light);
scene_.AddLight(directional_light);
scene_.AddLight(point_light);
directional_light.SetDirection(1.f, 1.f, 2.f);
point_light.SetPosition(300.f, 300.f, -2000.f);
point_light.SetFalloff(0.f);
// Explicitly set "UNSHADOWED" as the default shadow type. In addition to
// setting the param, this sets appropriate light intensities.
{
// When no shadows, ambient light needs to be full brightness. Otherwise,
// ambient needs to be dimmed so that other lights don't "overbrighten".
ambient_light.SetColor(1.f, 1.f, 1.f);
directional_light.SetColor(0.f, 0.f, 0.f);
point_light.SetColor(0.f, 0.f, 0.f);
fuchsia::ui::gfx::RendererParam param;
param.set_shadow_technique(fuchsia::ui::gfx::ShadowTechnique::UNSHADOWED);
renderer_.SetParam(std::move(param));
}
SetScenicDisplayRotation();
{
fuchsia::ui::views::ViewRef root_view_ref, injector_view_ref;
{ // Set up views and view holders.
{ // Set up the root view.
auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
fidl::Clone(view_ref, &root_view_ref);
root_view_holder_.emplace(root_session_.get(), std::move(internal_view_holder_token),
"Root View Holder");
root_view_.emplace(root_session_.get(), std::move(internal_view_token),
std::move(control_ref), std::move(view_ref), "Root View");
}
{ // Set up the injector view.
auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
fidl::Clone(view_ref, &injector_view_ref);
injector_view_holder_.emplace(root_session_.get(), std::move(internal_view_holder_token),
"Injector View Holder");
injector_view_.emplace(&injector_session_, std::move(internal_view_token),
std::move(control_ref), std::move(view_ref), "Injector View");
}
{ // Set up the "proxy view"
auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
proxy_view_holder_.emplace(&injector_session_, std::move(internal_view_holder_token),
"Proxy View Holder");
proxy_view_.emplace(&proxy_session_, std::move(internal_view_token), std::move(control_ref),
std::move(view_ref), "Proxy View");
}
// Connect it all up.
scene_.AddChild(root_view_holder_.value());
root_view_->AddChild(injector_view_holder_.value());
injector_view_->AddChild(proxy_view_holder_.value());
safe_presenter_root_.QueuePresent([this] { UpdateGraphState({.root_view_attached = true}); });
safe_presenter_injector_.QueuePresent(
[this] { UpdateGraphState({.injector_view_attached = true}); });
safe_presenter_proxy_.QueuePresent([] {});
}
injector_.emplace(component_context,
/*context=*/fidl::Clone(root_view_ref),
/*target=*/fidl::Clone(injector_view_ref),
fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET,
inspect_node_.CreateChild("Injector"));
// Sets up InjectorConfigSetup for input pipeline to receive view refs and viewport updates.
injector_config_setup_.emplace(component_context, /*context*/ std::move(root_view_ref),
/*target*/ std::move(injector_view_ref));
}
scenic->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) mutable {
InitializeDisplayModel(std::move(display_info));
safe_presenter_root_.QueuePresent([] {});
safe_presenter_injector_.QueuePresent([] {});
safe_presenter_proxy_.QueuePresent([] {});
});
proxy_session_.set_error_handler([](zx_status_t status) {
FX_LOGS(ERROR) << "Proxy session closed unexpectedly with status: "
<< zx_status_get_string(status);
});
injector_session_.set_error_handler([](zx_status_t status) {
FX_LOGS(ERROR) << "Injector session closed unexpectedly with status: "
<< zx_status_get_string(status);
});
proxy_session_.set_event_handler([this](std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.Which() != fuchsia::ui::scenic::Event::Tag::kGfx)
continue;
const auto& gfx_event = event.gfx();
if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewConnected) {
UpdateGraphState({.client_view_attached = true});
} else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewDisconnected) {
FX_LOGS(WARNING) << "Client View disconnected. Closing channel.";
proxy_view_->DetachChild(client_view_holder_.value());
client_view_holder_.reset();
safe_presenter_proxy_.QueuePresent([] {});
UpdateGraphState({.client_view_attached = false});
presentation_binding_.Unbind();
} else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene) {
UpdateGraphState({.proxy_view_attached = true});
} else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene) {
UpdateGraphState({.proxy_view_attached = false});
}
}
});
{
// TODO(fxbug.dev/68206) Remove this and enable client-side FIDL errors.
fidl::internal::TransitoryProxyControllerClientSideErrorDisabler client_side_error_disabler_;
component_context->svc()->Connect(magnifier_.NewRequest());
magnifier_->RegisterHandler(a11y_binding_.NewBinding());
a11y_binding_.set_error_handler([this](auto) { ResetClipSpaceTransform(); });
}
FX_DCHECK(root_view_holder_);
FX_DCHECK(root_view_);
FX_DCHECK(injector_view_holder_);
FX_DCHECK(injector_view_);
FX_DCHECK(proxy_view_holder_);
FX_DCHECK(proxy_view_);
FX_DCHECK(injector_);
}
void Presentation::UpdateGraphState(GraphState updated_state) {
// Replace anything that isn't std::nullopt.
// No (easy) way to iterate over a struct or a tuple, so we're left with brute force updating.
graph_state_.root_view_attached =
updated_state.root_view_attached.value_or(graph_state_.root_view_attached.value());
graph_state_.injector_view_attached =
updated_state.injector_view_attached.value_or(graph_state_.injector_view_attached.value());
graph_state_.a11y_view_attached =
updated_state.a11y_view_attached.value_or(graph_state_.a11y_view_attached.value());
graph_state_.proxy_view_attached =
updated_state.proxy_view_attached.value_or(graph_state_.proxy_view_attached.value());
graph_state_.client_view_attached =
updated_state.client_view_attached.value_or(graph_state_.client_view_attached.value());
if (graph_state_.client_view_attached.value() && create_a11y_view_holder_callback_) {
create_a11y_view_holder_callback_();
} else if (IsValidSceneGraph()) {
injector_->MarkSceneReady();
if (client_view_ref_.has_value()) {
FX_LOGS(INFO) << "Transferring focus to client";
view_focuser_->RequestFocus(fidl::Clone(client_view_ref_.value()), [](auto) {});
} else {
FX_LOGS(WARNING) << "Cannot transfer focus to client: no view ref.";
}
}
}
void Presentation::InitializeDisplayModel(fuchsia::ui::gfx::DisplayInfo display_info) {
FX_DCHECK(!display_model_initialized_);
display_model_initialized_ = true;
// Initialize display model.
display_configuration::InitializeModelForDisplay(display_info.width_in_px,
display_info.height_in_px, &display_model_);
display_metrics_ = display_model_.GetMetrics();
display_configuration::LogDisplayMetrics(display_metrics_);
// Today, a layer needs the display's physical dimensions to render correctly.
layer_.SetSize(static_cast<float>(display_metrics_.width_in_px()),
static_cast<float>(display_metrics_.height_in_px()));
SetViewHolderProperties(display_metrics_);
UpdateViewport(display_metrics_);
}
void Presentation::SetViewHolderProperties(const DisplayMetrics& display_metrics) {
const bool is_90_degree_rotation = abs(display_startup_rotation_adjustment_ % 180) == 90;
// Layout size
{
// Set the root view to native resolution and orientation (i.e. no rotation) of the display.
// This lets us delegate touch coordinate transformations to Scenic.
const float raw_metrics_width = static_cast<float>(display_metrics.width_in_px());
const float raw_metrics_height = static_cast<float>(display_metrics.height_in_px());
FX_DCHECK(root_view_holder_);
root_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, raw_metrics_width,
raw_metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
{ // Set all other views' resolutions to pips.
float metrics_width = static_cast<float>(display_metrics.width_in_pp());
float metrics_height = static_cast<float>(display_metrics.height_in_pp());
// Swap metrics on left/right tilt.
if (is_90_degree_rotation) {
std::swap(metrics_width, metrics_height);
}
// Injector, a11y, proxy, and client views should all have the same dimensions.
FX_DCHECK(injector_view_holder_);
injector_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width,
metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
FX_DCHECK(proxy_view_holder_);
proxy_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width,
metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
if (client_view_holder_) {
client_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width,
metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
}
FX_VLOGS(2) << "DisplayModel layout: " << metrics_width << ", " << metrics_height;
}
// Remaining transformations are only applied to the root view's child and automatically
// propagated down to the client view through the scene graph. The injector view holder is always
// the root's child.
{ // Scale a11y view to full device size.
float metrics_scale_x = display_metrics.x_scale_in_px_per_pp();
float metrics_scale_y = display_metrics.y_scale_in_px_per_pp();
// Swap metrics on left/right tilt.
if (is_90_degree_rotation) {
std::swap(metrics_scale_x, metrics_scale_y);
}
injector_view_holder_->SetScale(metrics_scale_x, metrics_scale_y, 1.f);
FX_VLOGS(2) << "DisplayModel pixel scale: " << metrics_scale_x << ", " << metrics_scale_y;
}
{ // Rotate root's child view to match desired display orientation.
const glm::quat display_rotation =
glm::quat(glm::vec3(0, 0, glm::radians<float>(display_startup_rotation_adjustment_)));
injector_view_holder_->SetRotation(display_rotation.x, display_rotation.y, display_rotation.z,
display_rotation.w);
}
{ // Adjust a11y view position for rotation.
const float metrics_w = display_metrics.width_in_px();
const float metrics_h = display_metrics.height_in_px();
float left_offset = 0;
float top_offset = 0;
uint32_t degrees_rotated = abs(display_startup_rotation_adjustment_ % 360);
switch (degrees_rotated) {
case 0:
left_offset = 0;
top_offset = 0;
break;
case 90:
left_offset = metrics_w;
top_offset = 0;
break;
case 180:
left_offset = metrics_w;
top_offset = metrics_h;
break;
case 270:
left_offset = 0;
top_offset = metrics_h;
break;
default:
FX_LOGS(ERROR) << "Unsupported rotation";
break;
}
injector_view_holder_->SetTranslation(left_offset, top_offset, 0.f);
FX_VLOGS(2) << "DisplayModel translation: " << left_offset << ", " << top_offset;
}
}
void Presentation::PresentView(
fuchsia::ui::views::ViewHolderToken view_holder_token,
fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) {
if (presentation_binding_.is_bound()) {
FX_LOGS(ERROR) << "Support for multiple simultaneous presentations has been removed. To "
"replace a view, use PresentOrReplaceView";
// Reject the request.
presentation_request.Close(ZX_ERR_ALREADY_BOUND);
return;
}
AttachClient(std::move(view_holder_token), std::nullopt, std::move(presentation_request));
}
void Presentation::PresentOrReplaceView(
fuchsia::ui::views::ViewHolderToken view_holder_token,
fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) {
AttachClient(std::move(view_holder_token), std::nullopt, std::move(presentation_request));
}
void Presentation::PresentOrReplaceView2(
fuchsia::ui::views::ViewHolderToken view_holder_token, fuchsia::ui::views::ViewRef view_ref,
fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) {
AttachClient(std::move(view_holder_token), std::move(view_ref), std::move(presentation_request));
}
void Presentation::AttachClient(
fuchsia::ui::views::ViewHolderToken view_holder_token,
std::optional<fuchsia::ui::views::ViewRef> view_ref,
fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) {
if (client_view_holder_) {
proxy_view_->DetachChild(client_view_holder_.value());
UpdateGraphState({.client_view_attached = false});
}
client_view_holder_.emplace(&proxy_session_, std::move(view_holder_token), "Client View Holder");
proxy_view_->AddChild(client_view_holder_.value());
if (display_model_initialized_) {
SetViewHolderProperties(display_metrics_);
}
client_view_ref_ = std::move(view_ref); // unconditional write, replacement semantics
presentation_binding_.Bind(std::move(presentation_request));
safe_presenter_proxy_.QueuePresent([] {});
}
void Presentation::UpdateViewport(const DisplayMetrics& display_metrics) {
// Viewport should match the visible part of the display 1:1. To do this we need to match the
// ClipSpaceTransform.
//
// Since the ClipSpaceTransform is defined in Vulkan NDC with scaling, and the Viewport is defined
// in pixel coordinates, we need to be able to transform offsets to pixel coordinates. This is
// done by multiplying by half the display length and inverting the scale.
//
// Because the ClipSpaceTransform is defined with its origin in the center, and the
// Viewport with its origin in the top left corner, we need to add a center offset to compensate.
// This turns out to be as simple as half the scaled display length minus half the ClipSpace
// length, which equals scale - 1 in NDC.
//
// Finally, because the ClipSpaceTransform and the Viewport transform are defined in opposite
// directions (camera to scene vs context to viewport), all the transforms should be inverted
// for the Viewport transform. This means an inverted scale and negative clip offsets.
//
const float display_width = static_cast<float>(display_metrics.width_in_px());
const float display_height = static_cast<float>(display_metrics.height_in_px());
const float inverted_scale = 1.f / clip_scale_;
const float ndc_to_pixel_x = inverted_scale * display_width * 0.5f;
const float ndc_to_pixel_y = inverted_scale * display_height * 0.5f;
const float center_offset_ndc = clip_scale_ - 1.f;
injector_->SetViewport({
.width = display_width,
.height = display_height,
.scale = inverted_scale,
.x_offset = ndc_to_pixel_x * (center_offset_ndc - clip_offset_x_),
.y_offset = ndc_to_pixel_y * (center_offset_ndc - clip_offset_y_),
});
injector_config_setup_->UpdateViewport(injector_->GetCurrentViewport());
}
void Presentation::OnDeviceAdded(ui_input::InputDeviceImpl* input_device) {
const uint32_t device_id = input_device->id();
FX_VLOGS(1) << "OnDeviceAdded: device_id=" << device_id;
FX_DCHECK(device_states_by_id_.count(device_id) == 0);
std::unique_ptr<ui_input::DeviceState> state;
if (input_device->descriptor()->sensor) {
ui_input::OnSensorEventCallback callback = [this](uint32_t device_id,
fuchsia::ui::input::InputReport event) {
OnSensorEvent(device_id, std::move(event));
};
state = std::make_unique<ui_input::DeviceState>(device_id, input_device->descriptor(),
std::move(callback));
} else {
ui_input::OnEventCallback callback = [this](fuchsia::ui::input::InputEvent event) {
OnEvent(std::move(event));
};
state = std::make_unique<ui_input::DeviceState>(device_id, input_device->descriptor(),
std::move(callback));
}
ui_input::DeviceState* state_ptr = state.get();
auto device_pair = std::make_pair(input_device, std::move(state));
state_ptr->OnRegistered();
device_states_by_id_.emplace(device_id, std::move(device_pair));
injector_->OnDeviceAdded(device_id);
}
void Presentation::OnDeviceRemoved(uint32_t device_id) {
FX_VLOGS(1) << "OnDeviceRemoved: device_id=" << device_id;
if (device_states_by_id_.count(device_id) != 0) {
device_states_by_id_[device_id].second->OnUnregistered();
device_states_by_id_.erase(device_id);
}
injector_->OnDeviceRemoved(device_id);
}
void Presentation::OnReport(uint32_t device_id, fuchsia::ui::input::InputReport input_report) {
// Media buttons should be processed by MediaButtonsHandler.
FX_DCHECK(!input_report.media_buttons);
TRACE_DURATION("input", "presentation_on_report", "id", input_report.trace_id);
TRACE_FLOW_END("input", "report_to_presentation", input_report.trace_id);
FX_VLOGS(2) << "OnReport device=" << device_id
<< ", count=" << device_states_by_id_.count(device_id) << ", report=" << input_report;
ChattyReportLog(input_report);
input_report_inspector_.OnInputReport(input_report);
if (device_states_by_id_.count(device_id) == 0) {
FX_VLOGS(1) << "OnReport: Unknown device " << device_id;
return;
}
if (!display_model_initialized_)
return;
ui_input::DeviceState* state = device_states_by_id_[device_id].second.get();
fuchsia::math::Size size;
size.width = display_model_.display_info().width_in_px;
size.height = display_model_.display_info().height_in_px;
TRACE_FLOW_BEGIN("input", "report_to_device_state", input_report.trace_id);
state->Update(std::move(input_report), size);
}
void Presentation::SetClipSpaceTransform(float x, float y, float scale,
SetClipSpaceTransformCallback callback) {
clip_offset_x_ = x;
clip_offset_y_ = y;
clip_scale_ = scale;
camera_.SetClipSpaceTransform(clip_offset_x_, clip_offset_y_, clip_scale_);
// The callback is used to throttle magnification transition animations and is expected to
// approximate the framerate.
safe_presenter_root_.QueuePresent([this, callback = std::move(callback)] {
UpdateViewport(display_metrics_);
callback();
});
}
void Presentation::ResetClipSpaceTransform() {
SetClipSpaceTransform(0, 0, 1, [] {});
}
void Presentation::OnEvent(fuchsia::ui::input::InputEvent event) {
TRACE_DURATION("input", "presentation_on_event");
FX_VLOGS(1) << "OnEvent " << event;
ChattyEventLog(event);
input_event_inspector_.OnInputEvent(event);
injector_->OnEvent(event);
}
void Presentation::OnSensorEvent(uint32_t device_id, fuchsia::ui::input::InputReport event) {
FX_VLOGS(2) << "OnSensorEvent(device_id=" << device_id << "): " << event;
FX_DCHECK(device_states_by_id_.count(device_id) > 0);
FX_DCHECK(device_states_by_id_[device_id].first);
FX_DCHECK(device_states_by_id_[device_id].first->descriptor());
FX_DCHECK(device_states_by_id_[device_id].first->descriptor()->sensor.get());
// No clients of sensor events at the moment.
}
void Presentation::SetScenicDisplayRotation() {
fuchsia::ui::gfx::Command command;
fuchsia::ui::gfx::SetDisplayRotationCmdHACK display_rotation_cmd;
display_rotation_cmd.compositor_id = compositor_.id();
display_rotation_cmd.rotation_degrees = display_startup_rotation_adjustment_;
command.set_set_display_rotation(std::move(display_rotation_cmd));
root_session_->Enqueue(std::move(command));
}
void Presentation::CreateAccessibilityViewHolder(
fuchsia::ui::views::ViewRef a11y_view_ref,
fuchsia::ui::views::ViewHolderToken a11y_view_holder_token,
CreateAccessibilityViewHolderCallback callback) {
if (!graph_state_.client_view_attached.value()) {
// Store a callback so that CreateAccessibilityViewHolder() is always called AFTER the
// client view is attached. Deferring this work prevents racy ordering issues, and a11y doesn't
// have anything to do when there's no client view anyway.
create_a11y_view_holder_callback_ =
[this, callback = std::move(callback), a11y_view_ref = std::move(a11y_view_ref),
a11y_view_holder_token = std::move(a11y_view_holder_token)]() mutable {
FX_DCHECK(graph_state_.client_view_attached.value());
CreateAccessibilityViewHolder(std::move(a11y_view_ref), std::move(a11y_view_holder_token),
std::move(callback));
};
return;
}
FX_CHECK(injector_view_);
FX_LOGS(INFO) << "Inserting A11y View";
// Detach proxy view holder from injector view.
injector_view_->DetachChild(proxy_view_holder_.value());
// Detach client view from proxy view, and delete proxy view and view holder objects (which
// frees the scenic resources).
if (client_view_holder_.has_value()) {
proxy_view_->DetachChild(client_view_holder_.value());
}
proxy_view_.reset();
proxy_view_holder_.reset();
UpdateGraphState(
{.a11y_view_attached = false, .proxy_view_attached = false, .client_view_attached = false});
// Generate new proxy view/view holder tokens, create a new proxy view.
// Note that we do not create a new proxy view holder here, because the a11y
// manager must own the new proxy view holder.
auto [proxy_view_token, proxy_view_holder_token] = scenic::ViewTokenPair::New();
auto [control_ref, view_ref] = scenic::ViewRefPair::New();
proxy_view_.emplace(&proxy_session_, std::move(proxy_view_token), std::move(control_ref),
std::move(view_ref), "Proxy View");
// Add the client view holder as a child of the new proxy view.
if (client_view_holder_.has_value()) {
proxy_view_->AddChild(client_view_holder_.value());
}
// Construct the a11y view holder.
proxy_view_holder_.emplace(&injector_session_, std::move(a11y_view_holder_token),
"A11y View Holder");
// Add the a11y view holder as a child of the injector view.
injector_view_->AddChild(proxy_view_holder_.value());
// Update view holder properties. Changes are presented below.
if (display_model_initialized_) {
SetViewHolderProperties(display_metrics_);
safe_presenter_root_.QueuePresent([] {});
}
safe_presenter_injector_.QueuePresent([this] { UpdateGraphState({.a11y_view_attached = true}); });
safe_presenter_proxy_.QueuePresent([this] { UpdateGraphState({.client_view_attached = true}); });
create_a11y_view_holder_callback_ = nullptr;
callback(std::move(proxy_view_holder_token));
}
} // namespace root_presenter