blob: 68ba3360a1378ad9dea723b02f70f9e22aac0b7c [file] [log] [blame]
// Copyright 2022 The Flutter 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/embedder/touch_delegate.h"
#include <lib/syslog/global.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include "src/embedder/fuchsia_logger.h"
#include "src/embedder/pointer_utility.h"
namespace fuchsia::ui::pointer {
// For using TouchInteractionId as a map key.
bool operator==(const fuchsia::ui::pointer::TouchInteractionId& a,
const fuchsia::ui::pointer::TouchInteractionId& b) {
return a.device_id == b.device_id && a.pointer_id == b.pointer_id &&
a.interaction_id == b.interaction_id;
}
} // namespace fuchsia::ui::pointer
namespace embedder {
namespace {
/// Checks the validity of a TouchEvent's pointer sample
/// and returns if one exists
bool HasValidatedTouchSample(const fup::TouchEvent& event) {
if (!event.has_pointer_sample()) {
return false;
}
FX_DCHECK(event.pointer_sample().has_interaction());
FX_DCHECK(event.pointer_sample().has_phase());
FX_DCHECK(event.pointer_sample().has_position_in_viewport());
return true;
}
// Helper to insert one or two events into a vector buffer.
void InsertIntoBuffer(std::pair<FlutterPointerEvent, std::optional<FlutterPointerEvent>> events,
std::vector<FlutterPointerEvent>* buffer) {
FX_DCHECK(buffer);
buffer->emplace_back(std::move(events.first));
if (events.second.has_value()) {
buffer->emplace_back(std::move(events.second.value()));
}
}
/// Translates EventPhase to FlutterPointerPhase
FlutterPointerPhase GetChangeFromTouchEventPhase(fup::EventPhase phase) {
switch (phase) {
case fup::EventPhase::ADD:
return FlutterPointerPhase::kAdd;
case fup::EventPhase::CHANGE:
return FlutterPointerPhase::kMove;
case fup::EventPhase::REMOVE:
return FlutterPointerPhase::kRemove;
case fup::EventPhase::CANCEL:
return FlutterPointerPhase::kCancel;
default:
return FlutterPointerPhase::kCancel;
}
}
/// TODO(benbergkamp) need to re-evaluate if this is best mechanism for
/// implementing multi-touch.
///
/// The return value is used to populate the device field which should
/// uniquely identify a pointer. We pack the device_id into the same field
/// to retain uniqueness across multiple touch device.
///
/// When migrating to embedder API, the device field was changed from 64 bits
/// to 32 bits. Therefore we are unable to pack the entire device_id and
/// pointer_id fields into this without losing any bits. We now need to
/// shift the IDs by only 16 bits so that we can fit their low bits into a
/// single 32 bit integer. The checks offer some comfort that we will not
/// be losing data by removing the high bits.
uint32_t PackFuchsiaDeviceIdAndPointerId(uint32_t fuchsia_device_id, uint32_t fuchsia_pointer_id) {
static constexpr uint32_t MASK = 0xFFFF0000;
FX_CHECK((fuchsia_device_id & MASK) == 0); // Ensure high bits are 0 & no loss of data
FX_CHECK((fuchsia_pointer_id & MASK) == 0); // Ensure high bits are 0 & no loss of data
return (((uint32_t)fuchsia_device_id) << 16) | (uint16_t)fuchsia_pointer_id;
}
/// It returns a "draft" because the coordinates are logical. Later, view pixel
/// ratio is applied to obtain physical coordinates.
///
/// The flutter pointerdata state machine has extra phases, which this function
/// synthesizes on the fly. Hence the return data is a flutter pointerdata, and
/// optionally a second one.
/// For example: <ADD, DOWN>, <MOVE, nullopt>, <UP, REMOVE>.
/// TODO(fxbug.dev/87074): Let PointerDataPacketConverter synthesize events.
///
/// Flutter gestures expect a gesture to start within the logical view space, and
/// is not tolerant of floating point drift. This function coerces just the DOWN
/// event's coordinate to start within the logical view.
std::pair<FlutterPointerEvent, std::optional<FlutterPointerEvent>> CreateTouchDraft(
const fup::TouchEvent& event, const fup::ViewParameters& view_parameters) {
const auto& sample = event.pointer_sample();
const auto& ixn = sample.interaction();
FlutterPointerEvent ptr;
pointer_utility::ResetFlutterPointerEvent(ptr);
ptr.timestamp = pointer_utility::ConvertEventTimeToMicroseconds(event.timestamp());
ptr.phase = GetChangeFromTouchEventPhase(sample.phase());
ptr.device_kind = FlutterPointerDeviceKind::kFlutterPointerDeviceKindTouch;
// Load Fuchsia's pointer ID onto Flutter's |device| field, and not the
// |pointer_identifier| field. The latter is written by
// PointerDataPacketConverter, to track individual gesture interactions.
ptr.device = PackFuchsiaDeviceIdAndPointerId(ixn.device_id, ixn.pointer_id);
// View parameters can change mid-interaction; apply transform on the fly.
auto logical = pointer_utility::ViewportToViewCoordinates(
sample.position_in_viewport(), view_parameters.viewport_to_view_transform);
ptr.x = logical[0]; // Not yet physical; adjusted in PlatformView.
ptr.y = logical[1]; // Not yet physical; adjusted in PlatformView.
// Match Flutter pointer's state machine with synthesized events.
if (ptr.phase == FlutterPointerPhase::kAdd) {
FlutterPointerEvent down;
memcpy(&down, &ptr, sizeof(FlutterPointerEvent));
down.phase = FlutterPointerPhase::kDown;
{ // Ensure gesture recognition: DOWN starts in the logical view space.
auto [x, y] = pointer_utility::ClampToViewSpace(down.x, down.y, view_parameters);
down.x = x;
down.y = y;
}
return {std::move(ptr), std::move(down)};
} else if (ptr.phase == FlutterPointerPhase::kRemove) {
FlutterPointerEvent up;
memcpy(&up, &ptr, sizeof(FlutterPointerEvent));
up.phase = FlutterPointerPhase::kUp;
return {std::move(up), std::move(ptr)};
} else {
return {std::move(ptr), std::nullopt};
}
}
} // namespace
TouchDelegate::TouchDelegate(fuchsia::ui::pointer::TouchSourceHandle touch_source)
: touch_source_(touch_source.Bind()) {
if (touch_source_) {
touch_source_.set_error_handler([](zx_status_t status) {
FX_LOGF(ERROR, kLogTag, "TouchSource channel error: %s", zx_status_get_string(status));
});
}
}
// Core logic of this class.
// Aim to keep state management in this function.
void TouchDelegate::WatchLoop(std::function<void(std::vector<FlutterPointerEvent>)> callback) {
if (touch_responder_) {
FX_LOG(ERROR, embedder::kLogTag, "TouchDelegate WatchLoop must only be called once");
return;
}
touch_responder_ = [this, callback](std::vector<fup::TouchEvent> events) {
std::vector<FlutterPointerEvent> to_client;
for (const fup::TouchEvent& event : events) {
pointer_utility::IssueInputTraceEvent(event);
fup::TouchResponse response; // Response per event, matched on event's index.
if (event.has_view_parameters()) {
touch_view_parameters_ = std::move(event.view_parameters());
}
if (HasValidatedTouchSample(event)) {
const auto& sample = event.pointer_sample();
auto ixn = sample.interaction();
// New stream without gesture disambiguation result --> buffer it
if (sample.phase() == fup::EventPhase::ADD && !event.has_interaction_result()) {
touch_buffer_.emplace(ixn, std::vector<FlutterPointerEvent>());
}
auto drafts = CreateTouchDraft(event, touch_view_parameters_.value());
if (touch_buffer_.count(ixn) > 0) {
InsertIntoBuffer(std::move(drafts), &touch_buffer_[ixn]);
} else {
InsertIntoBuffer(std::move(drafts), &to_client);
}
// For this simple client, always claim we want the gesture.
response.set_response_type(fup::TouchResponseType::YES);
}
if (event.has_interaction_result()) {
const auto& result = event.interaction_result();
const auto& ixn = result.interaction;
if (result.status == fup::TouchInteractionStatus::GRANTED && touch_buffer_.count(ixn) > 0) {
FX_DCHECK(to_client.empty());
to_client.insert(to_client.end(), touch_buffer_[ixn].begin(), touch_buffer_[ixn].end());
}
touch_buffer_.erase(ixn); // Result seen, delete the buffer.
}
touch_responses_.push_back(std::move(response));
}
callback(std::move(to_client));
touch_source_->Watch(std::move(touch_responses_),
/*copy*/ touch_responder_);
touch_responses_.clear();
};
touch_source_->Watch(std::move(touch_responses_),
/*copy*/ touch_responder_);
touch_responses_.clear();
}
} // namespace embedder