blob: 40b812bccc7c0c7105a9fb5caf8dda90eebd7f5a [file]
// Copyright 2026 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/scenic/lib/input/touch_source_v2.h"
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <zircon/status.h>
#include <algorithm>
#include "src/lib/fsl/handles/object_info.h"
namespace scenic_impl::input {
TouchSourceV2::TouchSourceV2(
async_dispatcher_t* dispatcher, zx_koid_t view_ref_koid,
fidl::ServerEnd<fuchsia_ui_pointer::TouchSourceV2> touch_source,
fit::function<void(StreamId, const std::vector<GestureResponse>&)> respond,
fit::function<void()> error_handler, GestureContenderInspector& inspector)
: TouchSourceBase(fsl::GetKoid(touch_source.channel().get()), view_ref_koid, std::move(respond),
inspector),
binding_(
dispatcher, std::move(touch_source), this,
[error_handler = std::move(error_handler)](fidl::UnbindInfo info) {
if (info.status() != ZX_OK && info.status() != ZX_ERR_PEER_CLOSED) {
FX_LOGS(ERROR) << "TouchSourceV2 fidl channel closed: " << info.FormatDescription();
} else {
FX_LOGS(INFO) << "TouchSourceV2 fidl channel closed: " << info.FormatDescription();
}
error_handler();
}) {}
void TouchSourceV2::UpdateStream(const view_tree::Snapshot& snapshot, StreamId stream_id,
InternalTouchEvent event, bool is_end_of_stream,
view_tree::BoundingBox view_bounds) {
if (is_closed_) {
// Satisfy Gesture Arena response contract to prevent hang even if channel is closed.
respond_(stream_id, {GestureResponse::kNo});
return;
}
TRACE_DURATION("input", "TouchSourceV2::UpdateStream");
TouchSourceBase::UpdateStream(snapshot, stream_id, std::move(event), is_end_of_stream,
view_bounds);
// V2 client is always-consume; report kYes to gesture disambiguation after
// the event has been enqueued into TouchSourceBase/ongoing_streams_.
// This satisfies the GestureArena contract (expecting exactly one response per
// event sample) while adapting to V2's always-consume semantics.
respond_(stream_id, always_yes_response_);
}
void TouchSourceV2::PushEvent(StreamId stream_id, AugmentedTouchEvent event) {
if (is_closed_) {
return;
}
QueueEvent(std::move(event.touch_event));
}
void TouchSourceV2::AcknowledgeEvents(AcknowledgeEventsRequest& request,
AcknowledgeEventsCompleter::Sync& completer) {
if (is_closed_) {
return;
}
uint64_t ack_stamp = request.last_acknowledged_event_stamp();
if (ack_stamp <= last_ack_stamp_ || ack_stamp > last_sent_stamp_) {
FX_LOGS(ERROR) << "TouchSourceV2: Invalid acknowledge stamp " << ack_stamp
<< " (last_ack=" << last_ack_stamp_ << ", last_sent=" << last_sent_stamp_
<< ").";
CloseChannel(ZX_ERR_INVALID_ARGS);
return;
}
last_ack_stamp_ = ack_stamp;
FlushPendingEvents();
}
void TouchSourceV2::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_ui_pointer::TouchSourceV2> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
FX_LOGS(WARNING) << "TouchSourceV2 received unknown method ordinal: " << metadata.method_ordinal;
}
void TouchSourceV2::CloseChannel(zx_status_t epitaph) {
if (is_closed_) {
return;
}
is_closed_ = true;
event_buffer_.clear();
FX_LOGS(WARNING) << "Closing TouchSourceV2 due to " << zx_status_get_string(epitaph);
binding_.Close(epitaph);
}
void TouchSourceV2::QueueEvent(fuchsia_ui_pointer::TouchEvent event) {
if (is_closed_) {
return;
}
++total_events_created_;
event_buffer_.push_back(
EventWithStamp{.event = std::move(event), .stamp = total_events_created_});
FlushPendingEvents();
}
void TouchSourceV2::FlushPendingEvents() {
if (is_closed_) {
return;
}
while (!event_buffer_.empty() &&
(last_sent_stamp_ - last_ack_stamp_) < kMaxUnacknowledgedEvents) {
uint64_t available_credit = kMaxUnacknowledgedEvents - (last_sent_stamp_ - last_ack_stamp_);
size_t batch_size = std::min<size_t>(
{available_credit, event_buffer_.size(), fuchsia_ui_pointer::kTouchMaxEvent});
if (batch_size == 0) {
break;
}
std::vector<fuchsia_ui_pointer::TouchEvent> events;
events.reserve(batch_size);
uint64_t last_stamp = 0;
for (size_t i = 0; i < batch_size; ++i) {
last_stamp = event_buffer_.front().stamp;
auto event = std::move(event_buffer_.front().event);
if (event.trace_flow_id().has_value()) {
// Touch events have their trace flow begun upstream in Injector, so step the flow here.
TRACE_FLOW_STEP("input", "dispatch_event_to_client", event.trace_flow_id().value());
}
events.push_back(std::move(event));
event_buffer_.pop_front();
}
inspector_.OnInjectedEvents(view_ref_koid_, events.size());
last_sent_stamp_ = last_stamp;
auto result = fidl::SendEvent(binding_)->OnTouchEvents(
fuchsia_ui_pointer::TouchSourceV2OnTouchEventsRequest{
{.events = std::move(events), .last_event_stamp = last_stamp}});
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Failed to send OnTouchEvents: " << result.error_value();
CloseChannel(result.error_value().status());
return;
}
}
if (event_buffer_.size() > kMaxBufferedEvents) {
FX_LOGS(ERROR)
<< "TouchSourceV2: Client is not acknowledging events fast enough. Closing channel.";
CloseChannel(ZX_ERR_BAD_STATE);
return;
}
}
} // namespace scenic_impl::input