blob: d9abb874b766970c8b822c4281ab5bc8cfca0baf [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.
#include "src/ui/bin/root_presenter/injector.h"
#include <lib/async/default.h>
#include <lib/async/time.h>
#include <lib/fostr/fidl/fuchsia/ui/input/formatting.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <zircon/status.h>
namespace root_presenter {
namespace {
// TODO(fxbug.dev/24476): Remove this.
// Turn two floats (high bits, low bits) into a 64-bit uint.
trace_flow_id_t PointerTraceHACK(float fa, float fb) {
uint32_t ia, ib;
memcpy(&ia, &fa, sizeof(uint32_t));
memcpy(&ib, &fb, sizeof(uint32_t));
return (((uint64_t)ia) << 32) | ib;
}
} // namespace
Injector::Injector(sys::ComponentContext* component_context, fuchsia::ui::views::ViewRef context,
fuchsia::ui::views::ViewRef target)
: component_context_(component_context),
context_view_ref_(std::move(context)),
target_view_ref_(std::move(target)) {
FX_DCHECK(component_context_);
}
void Injector::SetViewport(Viewport viewport) {
FX_VLOGS(2) << "SetViewport: width=" << viewport.width << " height=" << viewport.height
<< " scale=" << viewport.scale << " x_offset=" << viewport.x_offset
<< " y_offset=" << viewport.y_offset;
viewport_ = viewport;
// Update the viewport of all current injectors.
const int64_t now = async_now(async_get_default_dispatcher());
for (auto& [id, injector] : injectors_) {
fuchsia::ui::pointerinjector::Event event;
{
event.set_timestamp(now);
event.set_trace_flow_id(TRACE_NONCE());
fuchsia::ui::pointerinjector::Data data;
data.set_viewport(GetCurrentViewport());
event.set_data(std::move(data));
}
injector.pending_events.push_back(std::move(event));
}
}
void Injector::OnDeviceAdded(uint32_t device_id) {
const InjectorId injector_id = next_injector_id_++;
injector_id_by_device_id_[device_id] = injector_id;
SetupInputInjection(injector_id, device_id);
}
void Injector::OnDeviceRemoved(uint32_t device_id) {
FX_DCHECK(injector_id_by_device_id_.count(device_id));
// Clean up the corresponding injector.
const InjectorId injector_id = injector_id_by_device_id_[device_id];
injector_id_by_device_id_.erase(device_id);
FX_DCHECK(injectors_.count(injector_id));
if (injectors_[injector_id].pending_events.empty()) {
injectors_.erase(injector_id);
} else {
// If we have pending events, mark it to be killed when all pending events have been handled.
injectors_[injector_id].kill_when_empty = true;
}
}
void Injector::InjectPending(InjectorId injector_id) {
TRACE_DURATION("input", "inject_pending_events");
FX_DCHECK(injectors_.count(injector_id));
auto& injector = injectors_.at(injector_id);
if (injector.injection_in_flight || injector.pending_events.empty() || !scene_ready_) {
return;
}
injector.injection_in_flight = true;
std::vector<fuchsia::ui::pointerinjector::Event> events_to_inject;
size_t num_events = 0;
while (!injector.pending_events.empty() &&
num_events < fuchsia::ui::pointerinjector::MAX_INJECT) {
events_to_inject.emplace_back(std::move(injector.pending_events.front()));
injector.pending_events.pop_front();
++num_events;
}
for (const auto& event : events_to_inject) {
TRACE_FLOW_BEGIN("input", "dispatch_event_to_scenic", event.trace_flow_id());
}
injector.touch_injector->Inject(std::move(events_to_inject), [this, injector_id] {
if (num_failed_injection_attempts_ > 0) {
FX_LOGS(INFO) << "Injection successful after " << num_failed_injection_attempts_
<< " failed attempts.";
num_failed_injection_attempts_ = 0;
}
FX_DCHECK(injectors_.count(injector_id));
auto& injector = injectors_.at(injector_id);
injector.injection_in_flight = false;
// Drain the queue eagerly, instead of draining lazily (i.e. on receiving the next input
// event).
if (!injector.pending_events.empty()) {
InjectPending(injector_id);
} else if (injector.kill_when_empty) {
injectors_.erase(injector_id);
}
});
}
// Both the API for injecting into RootPresenter and the API for injecting into Scenic support
// vector-based reporting of contemporaneous events, but DeviceState doesn't support vector
// passthrough, so injection into Scenic may not be aligned on timestamp boundaries.
void Injector::OnEvent(const fuchsia::ui::input::InputEvent& event) {
TRACE_DURATION("input", "presentation_on_event");
FX_VLOGS(1) << "OnEvent " << event;
if (!event.is_pointer()) {
FX_LOGS(ERROR) << "Received unexpected event: \"" << event
<< "\". Only pointer input events are handled.";
return;
}
const auto& pointer = event.pointer();
const uint32_t device_id = pointer.device_id;
FX_DCHECK(injector_id_by_device_id_.count(device_id));
const InjectorId injector_id = injector_id_by_device_id_[device_id];
FX_DCHECK(injectors_.count(injector_id));
// TODO(fxbug.dev/24476): Use proper trace_id for tracing flow.
const trace_flow_id_t trace_id = PointerTraceHACK(pointer.radius_major, pointer.radius_minor);
TRACE_FLOW_END("input", "dispatch_event_to_presentation", trace_id);
fuchsia::ui::pointerinjector::EventPhase phase;
switch (pointer.phase) {
case fuchsia::ui::input::PointerEventPhase::ADD:
phase = fuchsia::ui::pointerinjector::EventPhase::ADD;
break;
case fuchsia::ui::input::PointerEventPhase::MOVE:
phase = fuchsia::ui::pointerinjector::EventPhase::CHANGE;
break;
case fuchsia::ui::input::PointerEventPhase::REMOVE:
phase = fuchsia::ui::pointerinjector::EventPhase::REMOVE;
break;
case fuchsia::ui::input::PointerEventPhase::CANCEL:
phase = fuchsia::ui::pointerinjector::EventPhase::CANCEL;
break;
default: {
FX_LOGS(ERROR) << "Received unexpected phase: " << pointer.phase;
return;
}
}
fuchsia::ui::pointerinjector::Event out_event;
{
out_event.set_timestamp(pointer.event_time);
out_event.set_trace_flow_id(trace_id);
{
fuchsia::ui::pointerinjector::PointerSample pointer_sample;
pointer_sample.set_pointer_id(pointer.pointer_id);
pointer_sample.set_phase(phase);
pointer_sample.set_position_in_viewport({pointer.x, pointer.y});
fuchsia::ui::pointerinjector::Data data;
data.set_pointer_sample(std::move(pointer_sample));
out_event.set_data(std::move(data));
}
}
injectors_.at(injector_id).pending_events.emplace_back(std::move(out_event));
InjectPending(injector_id);
}
fuchsia::ui::pointerinjector::Viewport Injector::GetCurrentViewport() {
fuchsia::ui::pointerinjector::Viewport viewport;
std::array<std::array<float, 2>, 2> extents{{/*min*/ {0, 0},
/*max*/ {viewport_.width, viewport_.height}}};
viewport.set_extents(extents);
viewport.set_viewport_to_context_transform(std::array<float, 9>{
viewport_.scale, 0, 0, // first column
0, viewport_.scale, 0, // second column
viewport_.x_offset, viewport_.y_offset, 1, // third column
});
return viewport;
}
void Injector::SetupInputInjection(InjectorId injector_id, uint32_t device_id) {
auto& injector = injectors_[injector_id];
injector.device_id = device_id;
if (!scene_ready_)
return;
fuchsia::ui::pointerinjector::Config config;
{
config.set_device_id(device_id);
config.set_device_type(fuchsia::ui::pointerinjector::DeviceType::TOUCH);
// TOP_HIT_AND_ANCESTORS_IN_TARGET means only views from |target| down may receive events. The
// events may go to the view with the top hit and its ancestors up to and including |target|.
// The final decision on who gets the event is determined by Scenic and client protocols.
config.set_dispatch_policy(
fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET);
config.set_viewport(GetCurrentViewport());
{ // Use the root view as the |context|. It is set up to match the native resolution of the
// display (same coordinate space as touchscreen events).
fuchsia::ui::pointerinjector::Context context;
fuchsia::ui::views::ViewRef context_clone;
fidl::Clone(context_view_ref_, &context_clone);
context.set_view(std::move(context_clone));
config.set_context(std::move(context));
}
{ // The a11y view is the |target| (must be a descendant of |context|). Hit tests start from
// this point in the scene graph and go down. Delivered events are transformed to local
// coordinate system of the receiver.
fuchsia::ui::pointerinjector::Target target;
fuchsia::ui::views::ViewRef target_clone;
fidl::Clone(target_view_ref_, &target_clone);
target.set_view(std::move(target_clone));
config.set_target(std::move(target));
}
}
component_context_->svc()->Connect<fuchsia::ui::pointerinjector::Registry>()->Register(
std::move(config), injectors_.at(injector_id).touch_injector.NewRequest(), [] {});
injector.touch_injector.set_error_handler([this, injector_id, device_id](zx_status_t error) {
++num_failed_injection_attempts_;
if (num_failed_injection_attempts_ % kLogFrequency == 1) {
FX_LOGS(ERROR) << "Input injection channel for device id " << device_id
<< " died with error: " << zx_status_get_string(error)
<< ". Num failed attempts: " << num_failed_injection_attempts_
<< ". Attempting recovery.";
}
// Move the binding onto the stack so we can replace it safely from within the closure.
const auto old_device_ptr = std::move(injectors_[injector_id].touch_injector);
injectors_[injector_id].touch_injector = {};
// Try to recover.
injectors_[injector_id].injection_in_flight = false;
SetupInputInjection(injector_id, device_id);
InjectPending(injector_id);
});
}
void Injector::MarkSceneReady() {
scene_ready_ = true;
for (const auto& [id, injector] : injectors_) {
SetupInputInjection(id, injector.device_id);
InjectPending(id);
}
}
} // namespace root_presenter