0

Handle transfer state received from Browser and process input events.

In this change we are introducing logic to handle input events received
on Viz.
- Input events received on Viz before state transfer are queued.
- Upon receiving state transfer input events in queue for the given
  sequence are flushed and forwarded to "correct"
  RenderInputRouterSupportAndroid.
- Any out of order state transfer received are being ignored. This can
  happen since the states can come over different pipes from different
  web contents.

Bug: 370506271
Change-Id: I4c7aa7da0ccc17c4a9c52882e54686d491a743ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6073287
Reviewed-by: Peter Kasting <pkasting@chromium.org>
Reviewed-by: Aman Verma <amanvr@google.com>
Reviewed-by: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: Stephen Nusko <nuskos@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Commit-Queue: Kartar Singh <kartarsingh@google.com>
Cr-Commit-Position: refs/heads/main@{#1402336}
This commit is contained in:
Kartar Singh
2025-01-06 05:43:49 -08:00
committed by Chromium LUCI CQ
parent bc3b061b6b
commit a4f9be41fd
11 changed files with 811 additions and 20 deletions

@ -414,6 +414,8 @@ class TimeBase {
kMicrosecondsPerHour * kHoursPerDay;
static constexpr int64_t kMicrosecondsPerWeek = kMicrosecondsPerDay * 7;
static constexpr int64_t kNanosecondsPerMicrosecond = 1000;
static constexpr int64_t kNanosecondsPerMillisecond =
kNanosecondsPerMicrosecond * kMicrosecondsPerMillisecond;
static constexpr int64_t kNanosecondsPerSecond =
kNanosecondsPerMicrosecond * kMicrosecondsPerSecond;

@ -421,6 +421,8 @@ viz_component("service") {
"frame_sinks/external_begin_frame_source_android.h",
"gl/throw_uncaught_exception.cc",
"gl/throw_uncaught_exception.h",
"input/android_state_transfer_handler.cc",
"input/android_state_transfer_handler.h",
"input/fling_scheduler_android.cc",
"input/fling_scheduler_android.h",
"input/render_input_router_support_android.cc",
@ -689,7 +691,9 @@ viz_source_set("unit_tests") {
"display/overlay_unittest.cc",
"frame_sinks/external_begin_frame_source_android_unittest.cc",
"frame_sinks/fling_scheduler_android_unittest.cc",
"input/android_state_transfer_handler_unittest.cc",
]
deps += [ "//ui/events:motionevent_jni_headers" ]
}
if (enable_vulkan) {

@ -0,0 +1,233 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/input/android_state_transfer_handler.h"
#include <utility>
#include "base/check_deref.h"
#include "base/notreached.h"
#include "ui/events/android/motion_event_android_native.h"
namespace viz {
namespace {
base::TimeTicks GetEventDowntime(const base::android::ScopedInputEvent& event) {
return base::TimeTicks::FromUptimeMillis(
AMotionEvent_getDownTime(event.a_input_event()) /
base::Time::kNanosecondsPerMillisecond);
}
} // namespace
AndroidStateTransferHandler::TransferState::TransferState(
base::WeakPtr<RenderInputRouterSupportAndroidInterface> support,
input::mojom::TouchTransferStatePtr state)
: rir_support(support), transfer_state(std::move(state)) {
CHECK(transfer_state);
}
AndroidStateTransferHandler::TransferState::~TransferState() = default;
AndroidStateTransferHandler::TransferState::TransferState(
TransferState&& other) {
rir_support = other.rir_support;
other.rir_support = nullptr;
transfer_state = std::move(other.transfer_state);
}
AndroidStateTransferHandler::AndroidStateTransferHandler() = default;
AndroidStateTransferHandler::~AndroidStateTransferHandler() = default;
void AndroidStateTransferHandler::StateOnTouchTransfer(
input::mojom::TouchTransferStatePtr state,
base::WeakPtr<RenderInputRouterSupportAndroidInterface> rir_support) {
TRACE_EVENT("viz", "AndroidStateTransferHandler::StateOnTouchTransfer");
EmitPendingTransfersHistogram();
// TODO(crbug.com/383323530): Convert it to CHECK once we are using
// AssociatedRemotes for passing state from Browser to Viz.
const bool state_received_out_of_order =
(!pending_transferred_states_.empty() &&
(state->down_time_ms <
pending_transferred_states_.back().transfer_state->down_time_ms));
if (state_received_out_of_order) {
TRACE_EVENT_INSTANT("viz", "OutOfOrderTransferStateDropped");
// Drop out of order state received.
// It is possible since the state transfers coming from different web
// contents come over different mojo pipes.
return;
}
MaybeDropEventsFromEarlierSequences(state);
pending_transferred_states_.emplace(rir_support, std::move(state));
if (pending_transferred_states_.size() > kMaxPendingTransferredStates) {
pending_transferred_states_.pop();
}
if (events_buffer_.empty()) {
return;
}
if (CanStartProcessingVizEvents(events_buffer_.front())) {
while (!events_buffer_.empty()) {
if (!state_for_curr_sequence_.has_value()) {
// This can happen if the whole sequence was received on Viz before the
// state could be transferred from Browser. Early out since we wouldn't
// want to process the events from next sequence if they made it to
// queue as well. `state_for_curr_sequence_` is reset at the end of
// touch sequence when touch up or cancel is seen in `HandleTouchEvent`
// method.
return;
}
HandleTouchEvent(std::move(events_buffer_.front()));
events_buffer_.pop();
}
}
}
bool AndroidStateTransferHandler::OnMotionEvent(
base::android::ScopedInputEvent input_event,
const FrameSinkId& root_frame_sink_id) {
TRACE_EVENT("input", "AndroidStateTransferHandler::OnMotionEvent");
const int action = AMotionEvent_getAction(input_event.a_input_event()) &
AMOTION_EVENT_ACTION_MASK;
if (ignore_remaining_touch_sequence_) {
if (action == AMOTION_EVENT_ACTION_CANCEL ||
action == AMOTION_EVENT_ACTION_UP) {
ignore_remaining_touch_sequence_ = false;
state_for_curr_sequence_.reset();
}
return true;
}
ValidateRootFrameSinkId(root_frame_sink_id);
if (state_for_curr_sequence_.has_value() ||
CanStartProcessingVizEvents(input_event)) {
HandleTouchEvent(std::move(input_event));
return true;
}
bool events_from_dropped_sequence =
!pending_transferred_states_.empty() &&
(GetEventDowntime(input_event) <
pending_transferred_states_.front().transfer_state->down_time_ms);
if (events_from_dropped_sequence) {
return true;
}
// Queue events until we can start processing events received directly on
// Viz.
// TODO(crbug.com/384424270): Coalesce touch moves instead of pushing
// individual events to queue.
events_buffer_.push(std::move(input_event));
// Always return true since we are receiving input on Viz after hit testing on
// Browser already determined that web contents are being hit.
return true;
}
bool AndroidStateTransferHandler::CanStartProcessingVizEvents(
const base::android::ScopedInputEvent& event) {
CHECK(!state_for_curr_sequence_.has_value());
if (pending_transferred_states_.empty()) {
return false;
}
const jlong j_event_down_time =
base::TimeTicks::FromJavaNanoTime(
AMotionEvent_getDownTime(event.a_input_event()))
.ToUptimeMillis();
base::TimeTicks event_down_time =
base::TimeTicks::FromUptimeMillis(j_event_down_time);
auto& state = pending_transferred_states_.front();
// Touch event corresponding to previous state transfer should be
// processed before next sequence starts.
if (event_down_time == state.transfer_state->down_time_ms) {
state_for_curr_sequence_.emplace(std::move(state));
pending_transferred_states_.pop();
return true;
}
CHECK_LT(event_down_time, state.transfer_state->down_time_ms);
return false;
}
void AndroidStateTransferHandler::MaybeDropEventsFromEarlierSequences(
const input::mojom::TouchTransferStatePtr& state) {
if (events_buffer_.empty()) {
return;
}
while (!events_buffer_.empty() &&
GetEventDowntime(events_buffer_.front()) < state->down_time_ms) {
events_buffer_.pop();
}
}
void AndroidStateTransferHandler::EmitPendingTransfersHistogram() {
const char* histogram_name;
if (state_for_curr_sequence_.has_value()) {
histogram_name = kPendingTransfersHistogramNonNull;
} else {
histogram_name = kPendingTransfersHistogramNull;
}
// We don't expect histogram value i.e. `pending_transferred_states_.size()`
// to be more than 3(`kMaxPendingTransferredStates`, but leaving histogram max
// to 10 to have some space to increase `kMaxPendingTransferredStates`.
base::UmaHistogramCustomCounts(histogram_name,
pending_transferred_states_.size(), /*min=*/1,
/*exclusive_max=*/10, /*buckets=*/10u);
}
void AndroidStateTransferHandler::HandleTouchEvent(
base::android::ScopedInputEvent input_event) {
CHECK(state_for_curr_sequence_.has_value() &&
GetEventDowntime(input_event) ==
state_for_curr_sequence_->transfer_state->down_time_ms);
if (!state_for_curr_sequence_->rir_support) {
const int action = AMotionEvent_getAction(input_event.a_input_event()) &
AMOTION_EVENT_ACTION_MASK;
if (action == AMOTION_EVENT_ACTION_CANCEL ||
action == AMOTION_EVENT_ACTION_UP) {
state_for_curr_sequence_.reset();
ignore_remaining_touch_sequence_ = false;
} else {
ignore_remaining_touch_sequence_ = true;
}
return;
}
// TOOD(crbug.com/370506271): Use correct pix_to_dip for creating events.
auto event = ui::MotionEventAndroidNative::Create(
std::move(input_event),
/* pix_to_dip= */ 1,
state_for_curr_sequence_->transfer_state->raw_y_offset);
state_for_curr_sequence_->rir_support->OnTouchEvent(
*event.get(), /* emit_histograms= */ true);
if (event->GetAction() == ui::MotionEvent::Action::UP ||
event->GetAction() == ui::MotionEvent::Action::CANCEL) {
state_for_curr_sequence_.reset();
}
}
void AndroidStateTransferHandler::ValidateRootFrameSinkId(
const FrameSinkId& root_frame_sink_id) {
CHECK(root_frame_sink_id.is_valid());
if (active_root_frame_sink_id_ != root_frame_sink_id) {
CHECK(!active_root_frame_sink_id_.is_valid());
active_root_frame_sink_id_ = root_frame_sink_id;
}
}
} // namespace viz

@ -0,0 +1,91 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_VIZ_SERVICE_INPUT_ANDROID_STATE_TRANSFER_HANDLER_H_
#define COMPONENTS_VIZ_SERVICE_INPUT_ANDROID_STATE_TRANSFER_HANDLER_H_
#include <optional>
#include "base/containers/queue.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "components/input/android/android_input_callback.h"
#include "components/input/render_input_router.mojom.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/service/input/render_input_router_support_android.h"
#include "components/viz/service/viz_service_export.h"
namespace viz {
// AndroidStateTransferHandler listens to input events coming from Android
// platform and receives |TouchTransferState| coming from Browser. Input events
// are queued until state for corresponding touch sequence is received from
// Browser, similarly state is queued until we started receiving input events
// from Android platform.
class VIZ_SERVICE_EXPORT AndroidStateTransferHandler
: public input::AndroidInputCallbackClient {
public:
AndroidStateTransferHandler();
~AndroidStateTransferHandler();
// AndroidInputCallbackClient implementation.
bool OnMotionEvent(base::android::ScopedInputEvent input_event,
const FrameSinkId& root_frame_sink_id) override;
// `rir_support`: RenderInputRouterSupport corresponding to root widget's
// FrameSinkId in TouchTransferState. `rir_support` can be null at the start
// or in the middle of sequence in case CompositorFrameSink got destroyed.
void StateOnTouchTransfer(
input::mojom::TouchTransferStatePtr state,
base::WeakPtr<RenderInputRouterSupportAndroidInterface> rir_support);
size_t GetEventsBufferSizeForTesting() const { return events_buffer_.size(); }
static constexpr const char* kPendingTransfersHistogramNonNull =
"Android.InputOnViz.Viz.PendingStateTransfers.NonNullCurrentState";
static constexpr const char* kPendingTransfersHistogramNull =
"Android.InputOnViz.Viz.PendingStateTransfers.NullCurrentState";
private:
bool CanStartProcessingVizEvents(
const base::android::ScopedInputEvent& event);
void HandleTouchEvent(base::android::ScopedInputEvent input_event);
void MaybeDropEventsFromEarlierSequences(
const input::mojom::TouchTransferStatePtr& state);
void EmitPendingTransfersHistogram();
void ValidateRootFrameSinkId(const FrameSinkId& root_frame_sink_id);
// We currently only support a single active root frame sink.
FrameSinkId active_root_frame_sink_id_;
bool ignore_remaining_touch_sequence_ = false;
struct TransferState {
TransferState(
base::WeakPtr<RenderInputRouterSupportAndroidInterface> support,
input::mojom::TouchTransferStatePtr transfer_state);
TransferState(TransferState&& other);
~TransferState();
base::WeakPtr<RenderInputRouterSupportAndroidInterface> rir_support;
input::mojom::TouchTransferStatePtr transfer_state;
};
// State corresponding to active touch sequence.
std::optional<TransferState> state_for_curr_sequence_ = std::nullopt;
// The list maintains sorted order by key `TouchTransferState.down_time_ms`.
// Any state transfer received out of order is dropped.
base::queue<TransferState> pending_transferred_states_;
static constexpr int kMaxPendingTransferredStates = 3;
// Stores input events until we have received state from Browser for the
// currently transferred touch sequence.
base::queue<base::android::ScopedInputEvent> events_buffer_;
};
} // namespace viz
#endif // COMPONENTS_VIZ_SERVICE_INPUT_ANDROID_STATE_TRANSFER_HANDLER_H_

@ -0,0 +1,421 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/input/android_state_transfer_handler.h"
#include <android/input.h>
#include <utility>
#include <vector>
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "ui/events/motionevent_jni_headers/MotionEvent_jni.h"
namespace viz {
using ::testing::_;
namespace {
constexpr int kAndroidActionDown = AMOTION_EVENT_ACTION_DOWN;
constexpr int kAndroidActionMove = AMOTION_EVENT_ACTION_MOVE;
constexpr int kAndroidActionUp = AMOTION_EVENT_ACTION_UP;
constexpr FrameSinkId kRootCompositorFrameSinkId = FrameSinkId(1, 1);
constexpr FrameSinkId kRootWidgetFrameSinkId = FrameSinkId(2, 3);
base::android::ScopedInputEvent GetInputEvent(jlong down_time_ms,
jlong event_time_ms,
int action,
float x,
float y) {
// Java_MotionEvent_obtain expects timestamps(down time, event time) obtained
// from |SystemClock#uptimeMillis()|.
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> java_motion_event =
JNI_MotionEvent::Java_MotionEvent_obtain(env, down_time_ms, event_time_ms,
action, x, y,
/*metaState=*/0);
const AInputEvent* native_event = nullptr;
if (__builtin_available(android 31, *)) {
native_event = AMotionEvent_fromJava(env, java_motion_event.obj());
}
CHECK(native_event);
return base::android::ScopedInputEvent(native_event);
}
struct TestInputStream {
base::TimeTicks down_time_ms;
std::vector<base::android::ScopedInputEvent> events;
};
TestInputStream GenerateEventsForSequence(int num_moves,
bool include_touch_up) {
static base::TimeTicks event_time =
base::TimeTicks::Now() - base::Milliseconds(100);
static float x = 100;
static float y = 200;
TestInputStream event_stream;
event_time += base::Milliseconds(8);
x += 5;
y += 5;
jlong down_time = event_time.ToUptimeMillis();
event_stream.down_time_ms = base::TimeTicks::FromUptimeMillis(down_time);
event_stream.events.push_back(GetInputEvent(
down_time, event_time.ToUptimeMillis(), kAndroidActionDown, x, y));
for (int i = 1; i <= num_moves; i++) {
event_time += base::Milliseconds(8);
x += 5;
y += 5;
event_stream.events.push_back(GetInputEvent(
down_time, event_time.ToUptimeMillis(), kAndroidActionMove, x, y));
}
if (include_touch_up) {
event_time += base::Milliseconds(8);
event_stream.events.push_back(GetInputEvent(
down_time, event_time.ToUptimeMillis(), kAndroidActionUp, x, y));
}
return event_stream;
}
} // namespace
class MockRenderInputRouterSupportAndroid
: public RenderInputRouterSupportAndroidInterface {
public:
virtual ~MockRenderInputRouterSupportAndroid() = default;
MOCK_METHOD((bool),
OnTouchEvent,
(const ui::MotionEventAndroid&, bool),
(override));
base::WeakPtr<RenderInputRouterSupportAndroidInterface> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void Destroy() { weak_factory_.InvalidateWeakPtrs(); }
private:
base::WeakPtrFactory<RenderInputRouterSupportAndroidInterface> weak_factory_{
this};
};
class AndroidStateTransferHandlerTest : public testing::Test {
public:
void SetUp() override {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_V) {
GTEST_SKIP()
<< "AndroidStateTransferHandlerTest is used only when InputOnViz "
"is enabled i.e. on Android V+";
}
}
protected:
MockRenderInputRouterSupportAndroid mock_rir_support_;
AndroidStateTransferHandler handler_;
};
// The order of events received:
// Down1 -> Move1 -> StateTransfer(for Down1)
TEST_F(AndroidStateTransferHandlerTest, EventsProcessedOnStateTransfer) {
TestInputStream event_stream = GenerateEventsForSequence(
/*num_moves*/ 1,
/*include_touch_up*/ false);
for (auto& event : event_stream.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 2u);
auto state = input::mojom::TouchTransferState::New();
state->down_time_ms = event_stream.down_time_ms;
state->root_widget_frame_sink_id = kRootWidgetFrameSinkId;
// Both the events should be dequeued and processed.
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(2);
handler_.StateOnTouchTransfer(std::move(state),
mock_rir_support_.GetWeakPtr());
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// Down1 -> Move1 -> StateTransfer(for Down1)
// The rir_support for widget referred in state is already destroyed.
TEST_F(AndroidStateTransferHandlerTest, RIRSupportNullOnStateTransfer) {
TestInputStream event_stream = GenerateEventsForSequence(
/*num_moves*/ 1,
/*include_touch_up*/ false);
for (auto& event : event_stream.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 2u);
auto state = input::mojom::TouchTransferState::New();
state->down_time_ms = event_stream.down_time_ms;
state->root_widget_frame_sink_id = kRootWidgetFrameSinkId;
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(0);
handler_.StateOnTouchTransfer(std::move(state),
/* rir_support= */ nullptr);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// StateTransfer(for Down1) -> Down1 -> Move1 -> OnDestroyedCompositorFrameSink
// -> Move3.
// Move3 ends up getting dropped.
TEST_F(AndroidStateTransferHandlerTest,
InputReceivingCompositorFrameSinkDestroyedMidSequence) {
TestInputStream event_stream = GenerateEventsForSequence(
/*num_moves*/ 2,
/*include_touch_up*/ false);
auto state = input::mojom::TouchTransferState::New();
state->down_time_ms = event_stream.down_time_ms;
state->root_widget_frame_sink_id = kRootWidgetFrameSinkId;
handler_.StateOnTouchTransfer(std::move(state),
mock_rir_support_.GetWeakPtr());
handler_.OnMotionEvent(std::move(event_stream.events[0]),
kRootCompositorFrameSinkId);
handler_.OnMotionEvent(std::move(event_stream.events[1]),
kRootCompositorFrameSinkId);
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(0);
mock_rir_support_.Destroy();
// The events are dropped after the render input router support is
// destroyed.
handler_.OnMotionEvent(std::move(event_stream.events[2]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// StateTransfer(for Down1) -> Down1 -> Move1
// Down1 and Move1 can be immediately processed.
TEST_F(AndroidStateTransferHandlerTest, StateReceivedBeforeTouchDownOnViz) {
TestInputStream event_stream = GenerateEventsForSequence(
/*num_moves*/ 2,
/*include_touch_up*/ false);
auto state = input::mojom::TouchTransferState::New();
state->down_time_ms = event_stream.down_time_ms;
state->root_widget_frame_sink_id = kRootWidgetFrameSinkId;
handler_.StateOnTouchTransfer(std::move(state),
mock_rir_support_.GetWeakPtr());
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(1);
handler_.OnMotionEvent(std::move(event_stream.events[0]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(1);
handler_.OnMotionEvent(std::move(event_stream.events[1]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// StateTransfer(for Down1) -> Down1 -> Move1 -> StateTransfer(for Down2) -> Up1
// -> Down2 Down1 and move1 can be immediately processed.
TEST_F(AndroidStateTransferHandlerTest, NewStateReceivedMidSequence) {
TestInputStream event_stream_1 =
GenerateEventsForSequence(/*num_moves*/ 1,
/*include_touch_up*/ true);
TestInputStream event_stream_2 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ false);
// State is received before receiving any touch events directly on Viz
auto state1 = input::mojom::TouchTransferState::New();
state1->down_time_ms = event_stream_1.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state1),
mock_rir_support_.GetWeakPtr());
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(3);
// Down1 is received.
handler_.OnMotionEvent(std::move(event_stream_1.events[0]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
// Move1 is received.
handler_.OnMotionEvent(std::move(event_stream_1.events[1]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
// State for second sequence is received.
MockRenderInputRouterSupportAndroid mock_rir_support2;
auto state2 = input::mojom::TouchTransferState::New();
state2->down_time_ms = event_stream_2.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state2),
mock_rir_support2.GetWeakPtr());
// Up1 is received.
handler_.OnMotionEvent(std::move(event_stream_1.events[2]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
// Down1 is received from second sequence, on different
// RenderInputRouterSupport.
EXPECT_CALL(mock_rir_support2, OnTouchEvent(_, _)).Times(1);
handler_.OnMotionEvent(std::move(event_stream_2.events[0]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// Down1 -> Move1 -> Up1 -> Down2 -> StateTransfer(for Down1)
// Down1 and move1 can be immediately processed.
TEST_F(AndroidStateTransferHandlerTest,
FullSequenceReceivedBeforeStateTransfer) {
TestInputStream event_stream_1 =
GenerateEventsForSequence(/*num_moves*/ 1,
/*include_touch_up*/ true);
TestInputStream event_stream_2 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ false);
for (auto& event : event_stream_1.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 3u);
for (auto& event : event_stream_2.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 4u);
// State is received before receiving any touch events directly on Viz
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(3);
auto state1 = input::mojom::TouchTransferState::New();
state1->down_time_ms = event_stream_1.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state1),
mock_rir_support_.GetWeakPtr());
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 1u);
}
// The order of events received:
// StateTransfer(Down2) -> Down1 -> Up1 -> Down2
TEST_F(AndroidStateTransferHandlerTest,
InputEventsAreNotQueuedForDroppedStateTransfer) {
TestInputStream event_stream_1 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ true);
TestInputStream event_stream_2 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ false);
auto state2 = input::mojom::TouchTransferState::New();
state2->down_time_ms = event_stream_2.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state2),
mock_rir_support_.GetWeakPtr());
for (auto& event : event_stream_1.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(1);
for (auto& event : event_stream_2.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
}
// The order of events received:
// Down1 -> Up1 -> Down2 -> Up2 -> Down3 -> StateTransfer(Down3)
TEST_F(AndroidStateTransferHandlerTest,
QueuedInputEventsDroppedUponLaterStateTransfer) {
TestInputStream event_stream_1 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ true);
TestInputStream event_stream_2 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ true);
TestInputStream event_stream_3 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ false);
for (auto& event : event_stream_1.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
for (auto& event : event_stream_2.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
for (auto& event : event_stream_3.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(),
event_stream_1.events.size() + event_stream_2.events.size() +
event_stream_3.events.size());
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(1);
auto state3 = input::mojom::TouchTransferState::New();
state3->down_time_ms = event_stream_3.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state3),
mock_rir_support_.GetWeakPtr());
}
// The order of events received:
// StateTransfer(Down1) -> Down1 -> Move1 -> OnDestroyedCompositorFrameSink ->
// Up1 -> Down2 -> StateTransfer(Down2)
TEST_F(AndroidStateTransferHandlerTest, RirNullOnLastInputInSequence) {
TestInputStream event_stream_1 =
GenerateEventsForSequence(/*num_moves*/ 1,
/*include_touch_up*/ true);
TestInputStream event_stream_2 =
GenerateEventsForSequence(/*num_moves*/ 0,
/*include_touch_up*/ false);
auto state1 = input::mojom::TouchTransferState::New();
state1->down_time_ms = event_stream_1.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state1),
mock_rir_support_.GetWeakPtr());
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(2);
handler_.OnMotionEvent(std::move(event_stream_1.events[0]),
kRootCompositorFrameSinkId);
handler_.OnMotionEvent(std::move(event_stream_1.events[1]),
kRootCompositorFrameSinkId);
mock_rir_support_.Destroy();
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(0);
handler_.OnMotionEvent(std::move(event_stream_1.events[2]),
kRootCompositorFrameSinkId);
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 0u);
for (auto& event : event_stream_2.events) {
handler_.OnMotionEvent(std::move(event), kRootCompositorFrameSinkId);
}
EXPECT_EQ(handler_.GetEventsBufferSizeForTesting(), 1u);
EXPECT_CALL(mock_rir_support_, OnTouchEvent(_, _)).Times(1);
auto state2 = input::mojom::TouchTransferState::New();
state2->down_time_ms = event_stream_2.down_time_ms;
handler_.StateOnTouchTransfer(std::move(state2),
mock_rir_support_.GetWeakPtr());
}
} // namespace viz

@ -381,7 +381,18 @@ std::optional<bool> InputManager::IsDelegatedInkHovering(
}
void InputManager::StateOnTouchTransfer(
input::mojom::TouchTransferStatePtr state) {
// TODO(crbug.com/370506271): Handle state to start processing input events.
#if BUILDFLAG(IS_ANDROID)
auto iter = frame_sink_metadata_map_.find(state->root_widget_frame_sink_id);
base::WeakPtr<RenderInputRouterSupportAndroidInterface>
support_android_interface = nullptr;
if (iter != frame_sink_metadata_map_.end()) {
auto* support_android = static_cast<RenderInputRouterSupportAndroid*>(
iter->second.rir_support.get());
support_android_interface = support_android->GetWeakPtr();
}
android_state_transfer_handler_.StateOnTouchTransfer(
std::move(state), support_android_interface);
#endif
}
void InputManager::NotifySiteIsMobileOptimized(
@ -512,7 +523,8 @@ void InputManager::CreateOrReuseAndroidInputReceiver(
}
std::unique_ptr<input::AndroidInputCallback> android_input_callback =
std::make_unique<input::AndroidInputCallback>(frame_sink_id, this);
std::make_unique<input::AndroidInputCallback>(
frame_sink_id, &android_state_transfer_handler_);
// Destructor of |ScopedInputReceiverCallbacks| will call
// |AInputReceiverCallbacks_release|, so we don't have to explicitly unset the
// motion event callback we set below using
@ -560,16 +572,6 @@ void InputManager::CreateOrReuseAndroidInputReceiver(
std::move(receiver), std::move(viz_input_token));
}
bool InputManager::OnMotionEvent(base::android::ScopedInputEvent input_event,
const FrameSinkId& root_frame_sink_id) {
// TODO(370506271): Implement once we do the state transfer from Browser on
// touch down.
// Always return true since we are receiving input on Viz after hit testing on
// Browser already determined that web contents are being hit.
return true;
}
BeginFrameSource* InputManager::GetBeginFrameSourceForFrameSink(
const FrameSinkId& id) {
return frame_sink_manager_->GetFrameSinkForId(id)->begin_frame_source();

@ -21,9 +21,10 @@
#include "gpu/ipc/common/surface_handle.h"
#if BUILDFLAG(IS_ANDROID)
#include "components/input/android/android_input_callback.h"
#include "components/input/android/input_receiver_data.h"
#include "components/viz/service/input/android_state_transfer_handler.h"
#include "components/viz/service/input/fling_scheduler_android.h"
#include "components/viz/service/input/render_input_router_support_android.h"
#endif
namespace input {
@ -55,7 +56,6 @@ class VIZ_SERVICE_EXPORT InputManager
: public FrameSinkObserver,
public input::RenderWidgetHostInputEventRouter::Delegate,
#if BUILDFLAG(IS_ANDROID)
public input::AndroidInputCallbackClient,
public FlingSchedulerAndroid::Delegate,
#endif
public RenderInputRouterSupportBase::Delegate,
@ -98,10 +98,6 @@ class VIZ_SERVICE_EXPORT InputManager
const FrameSinkId& frame_sink_id) override;
#if BUILDFLAG(IS_ANDROID)
// AndroidInputCallbackClient implementation.
bool OnMotionEvent(base::android::ScopedInputEvent input_event,
const FrameSinkId& root_frame_sink_id) override;
// FlingSchedulerAndroid::Delegate implementation.
BeginFrameSource* GetBeginFrameSourceForFrameSink(
const FrameSinkId& id) override;
@ -167,6 +163,8 @@ class VIZ_SERVICE_EXPORT InputManager
const FrameSinkId& frame_sink_id,
const gpu::SurfaceHandle& surface_handle);
AndroidStateTransferHandler android_state_transfer_handler_;
std::unique_ptr<input::InputReceiverData> receiver_data_;
#endif // BUILDFLAG(IS_ANDROID)

@ -143,4 +143,9 @@ void RenderInputRouterSupportAndroid::GestureEventAck(
StopFlingingIfNecessary(event, ack_result);
}
base::WeakPtr<RenderInputRouterSupportAndroid>
RenderInputRouterSupportAndroid::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace viz

@ -7,6 +7,7 @@
#include <memory>
#include "base/memory/weak_ptr.h"
#include "components/input/android_input_helper.h"
#include "components/input/events_helper.h"
#include "components/viz/service/input/render_input_router_support_base.h"
@ -15,10 +16,19 @@
namespace viz {
// Allow easy testing of code calling into RenderInputRouterSupport's
// OnTouchEvent.
class RenderInputRouterSupportAndroidInterface {
public:
virtual bool OnTouchEvent(const ui::MotionEventAndroid& event,
bool emit_histograms) = 0;
};
class VIZ_SERVICE_EXPORT RenderInputRouterSupportAndroid
: public RenderInputRouterSupportBase,
public ui::GestureProviderClient,
public input::AndroidInputHelper::Delegate {
public input::AndroidInputHelper::Delegate,
public RenderInputRouterSupportAndroidInterface {
public:
explicit RenderInputRouterSupportAndroid(
input::RenderInputRouter* rir,
@ -35,7 +45,8 @@ class VIZ_SERVICE_EXPORT RenderInputRouterSupportAndroid
// |emit_histograms|: Whether to emit tool type and OS touch latency
// histograms, for the events forwarded from Browser we wouldn't want to emit
// histograms for them since Browser code would have already emitted them.
bool OnTouchEvent(const ui::MotionEventAndroid& event, bool emit_histograms);
bool OnTouchEvent(const ui::MotionEventAndroid& event,
bool emit_histograms) override;
bool ShouldRouteEvents() const;
// ui::GestureProviderClient implementation.
@ -64,12 +75,16 @@ class VIZ_SERVICE_EXPORT RenderInputRouterSupportAndroid
void SendGestureEvent(const blink::WebGestureEvent& event) override;
ui::FilteredGestureProvider& GetGestureProvider() override;
base::WeakPtr<RenderInputRouterSupportAndroid> GetWeakPtr();
private:
std::unique_ptr<input::AndroidInputHelper> input_helper_;
// Provides gesture synthesis given a stream of touch events (derived from
// Android MotionEvent's) and touch event acks.
ui::FilteredGestureProvider gesture_provider_;
base::WeakPtrFactory<RenderInputRouterSupportAndroid> weak_factory_{this};
};
} // namespace viz

@ -44,6 +44,8 @@ InputTransferHandlerAndroid::InputTransferHandlerAndroid(
InputTransferHandlerAndroid::~InputTransferHandlerAndroid() = default;
bool InputTransferHandlerAndroid::OnTouchEvent(const ui::MotionEvent& event) {
// TODO(crbug.com/383307455): Forward events seen on Browser post transfer
// over to Viz.
if (touch_transferred_) {
// TODO(crbug.com/370506271): Add support for getDownTime in MotionEvent and
// check if this cancel has same downtime as the original down used for

@ -2213,6 +2213,24 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Android.InputOnViz.Viz.PendingStateTransfers.{CurrentState}"
units="counts" expires_after="2025-06-30">
<owner>kartarsingh@google.com</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
When `InputOnViz` is enabled Browser process sends state on TouchDown to Viz
in case touch transfer was successful. In theory, due to hiccups in system
we can have multiple pending states on Viz.
This metric records the number of pending states on Viz when {CurrentState}.
This would help us verify if we have pending states in practice as well.
</summary>
<token key="CurrentState">
<variant name="NonNullCurrentState" summary="current state is non-null"/>
<variant name="NullCurrentState" summary="current state is null"/>
</token>
</histogram>
<histogram name="Android.Intent.HasNonSpoofablePackageName" enum="Boolean"
expires_after="2024-12-01">
<owner>katzz@google.com</owner>