/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../trace/AndroidInputEventProtoConverter.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "InputTracingBackendInterface.h"

namespace android::inputdispatcher::trace {

namespace {

using testing::Return, testing::_;

using input_trace::AndroidInputEventProtoConverter;
using input_trace::TracedKeyEvent;
using input_trace::TracedMotionEvent;
using input_trace::WindowDispatchArgs;

class MockProtoAxisValue {
public:
    MOCK_METHOD(void, set_axis, (int32_t));
    MOCK_METHOD(void, set_value, (float));
};

class MockProtoPointer {
public:
    MOCK_METHOD(void, set_pointer_id, (uint32_t));
    MOCK_METHOD(void, set_tool_type, (int32_t));
    MOCK_METHOD(MockProtoAxisValue*, add_axis_value, ());
};

class MockProtoMotion {
public:
    MOCK_METHOD(void, set_event_id, (uint32_t));
    MOCK_METHOD(void, set_event_time_nanos, (int64_t));
    MOCK_METHOD(void, set_down_time_nanos, (int64_t));
    MOCK_METHOD(void, set_source, (uint32_t));
    MOCK_METHOD(void, set_action, (int32_t));
    MOCK_METHOD(void, set_device_id, (uint32_t));
    MOCK_METHOD(void, set_display_id, (uint32_t));
    MOCK_METHOD(void, set_classification, (int32_t));
    MOCK_METHOD(void, set_flags, (uint32_t));
    MOCK_METHOD(void, set_policy_flags, (uint32_t));
    MOCK_METHOD(void, set_button_state, (uint32_t));
    MOCK_METHOD(void, set_action_button, (uint32_t));
    MOCK_METHOD(void, set_cursor_position_x, (float));
    MOCK_METHOD(void, set_cursor_position_y, (float));
    MOCK_METHOD(void, set_meta_state, (uint32_t));
    MOCK_METHOD(void, set_precision_x, (float));
    MOCK_METHOD(void, set_precision_y, (float));
    MOCK_METHOD(MockProtoPointer*, add_pointer, ());
};

class MockProtoKey {
public:
    MOCK_METHOD(void, set_event_id, (uint32_t));
    MOCK_METHOD(void, set_event_time_nanos, (int64_t));
    MOCK_METHOD(void, set_down_time_nanos, (int64_t));
    MOCK_METHOD(void, set_source, (uint32_t));
    MOCK_METHOD(void, set_action, (int32_t));
    MOCK_METHOD(void, set_device_id, (uint32_t));
    MOCK_METHOD(void, set_display_id, (uint32_t));
    MOCK_METHOD(void, set_repeat_count, (uint32_t));
    MOCK_METHOD(void, set_flags, (uint32_t));
    MOCK_METHOD(void, set_policy_flags, (uint32_t));
    MOCK_METHOD(void, set_key_code, (uint32_t));
    MOCK_METHOD(void, set_scan_code, (uint32_t));
    MOCK_METHOD(void, set_meta_state, (uint32_t));
};

class MockProtoDispatchPointer {
public:
    MOCK_METHOD(void, set_pointer_id, (uint32_t));
    MOCK_METHOD(void, set_x_in_display, (float));
    MOCK_METHOD(void, set_y_in_display, (float));
    MOCK_METHOD(MockProtoAxisValue*, add_axis_value_in_window, ());
};

class MockProtoDispatch {
public:
    MOCK_METHOD(void, set_event_id, (uint32_t));
    MOCK_METHOD(void, set_vsync_id, (uint32_t));
    MOCK_METHOD(void, set_window_id, (uint32_t));
    MOCK_METHOD(void, set_resolved_flags, (uint32_t));
    MOCK_METHOD(MockProtoDispatchPointer*, add_dispatched_pointer, ());
};

class MockProtoEvdev {
    // Currently empty as it's not used in tests.
};

using TestProtoConverter =
        AndroidInputEventProtoConverter<MockProtoMotion, MockProtoKey, MockProtoDispatch,
                                        MockProtoEvdev, proto::AndroidInputEventConfig::Decoder>;

TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent) {
    TracedMotionEvent event{};
    event.id = 1;
    event.eventTime = 2;
    event.downTime = 3;
    event.source = AINPUT_SOURCE_MOUSE;
    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
    event.deviceId = 4;
    event.displayId = ui::LogicalDisplayId(5);
    event.classification = MotionClassification::PINCH;
    event.flags = MotionFlag::CANCELED;
    event.policyFlags = 7;
    event.buttonState = 8;
    event.actionButton = 9;
    event.xCursorPosition = 10.0f;
    event.yCursorPosition = 11.0f;
    event.metaState = 12;
    event.xPrecision = 13.0f;
    event.yPrecision = 14.0f;
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 15,
            .toolType = ToolType::MOUSE,
    });
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 16,
            .toolType = ToolType::FINGER,
    });
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 17.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 18.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 19.0f);
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 20.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 21.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22.0f);

    testing::StrictMock<MockProtoMotion> proto;
    testing::StrictMock<MockProtoPointer> pointer1;
    testing::StrictMock<MockProtoPointer> pointer2;
    testing::StrictMock<MockProtoAxisValue> axisValue1;
    testing::StrictMock<MockProtoAxisValue> axisValue2;
    testing::StrictMock<MockProtoAxisValue> axisValue3;
    testing::StrictMock<MockProtoAxisValue> axisValue4;
    testing::StrictMock<MockProtoAxisValue> axisValue5;
    testing::StrictMock<MockProtoAxisValue> axisValue6;

    EXPECT_CALL(proto, set_event_id(1));
    EXPECT_CALL(proto, set_event_time_nanos(2));
    EXPECT_CALL(proto, set_down_time_nanos(3));
    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
    EXPECT_CALL(proto, set_device_id(4));
    EXPECT_CALL(proto, set_display_id(5));
    EXPECT_CALL(proto, set_classification(AMOTION_EVENT_CLASSIFICATION_PINCH));
    EXPECT_CALL(proto, set_flags(ftl::Flags<MotionFlag>(MotionFlag::CANCELED).get()));
    EXPECT_CALL(proto, set_policy_flags(7));
    EXPECT_CALL(proto, set_button_state(8));
    EXPECT_CALL(proto, set_action_button(9));
    EXPECT_CALL(proto, set_cursor_position_x(10.0f));
    EXPECT_CALL(proto, set_cursor_position_y(11.0f));
    EXPECT_CALL(proto, set_meta_state(12));
    EXPECT_CALL(proto, set_precision_x(13.0f));
    EXPECT_CALL(proto, set_precision_y(14.0f));

    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));

    EXPECT_CALL(pointer1, set_pointer_id(15));
    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
    EXPECT_CALL(pointer1, add_axis_value())
            .WillOnce(Return(&axisValue1))
            .WillOnce(Return(&axisValue2))
            .WillOnce(Return(&axisValue3));
    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue1, set_value(17.0f));
    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue2, set_value(18.0f));
    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
    EXPECT_CALL(axisValue3, set_value(19.0f));

    EXPECT_CALL(pointer2, set_pointer_id(16));
    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
    EXPECT_CALL(pointer2, add_axis_value())
            .WillOnce(Return(&axisValue4))
            .WillOnce(Return(&axisValue5))
            .WillOnce(Return(&axisValue6));
    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue4, set_value(20.0f));
    EXPECT_CALL(axisValue5, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue5, set_value(21.0f));
    EXPECT_CALL(axisValue6, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
    EXPECT_CALL(axisValue6, set_value(22.0f));

    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/false);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent_Redacted) {
    TracedMotionEvent event{};
    event.id = 1;
    event.eventTime = 2;
    event.downTime = 3;
    event.source = AINPUT_SOURCE_MOUSE;
    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
    event.deviceId = 4;
    event.displayId = ui::LogicalDisplayId(5);
    event.classification = MotionClassification::PINCH;
    event.flags = MotionFlag::CANCELED;
    event.policyFlags = 7;
    event.buttonState = 8;
    event.actionButton = 9;
    event.xCursorPosition = 10.0f;
    event.yCursorPosition = 11.0f;
    event.metaState = 12;
    event.xPrecision = 13.0f;
    event.yPrecision = 14.0f;
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 15,
            .toolType = ToolType::MOUSE,
    });
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 16,
            .toolType = ToolType::FINGER,
    });
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 17.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 18.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 19.0f);
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 20.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 21.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22.0f);

    testing::StrictMock<MockProtoMotion> proto;
    testing::StrictMock<MockProtoPointer> pointer1;
    testing::StrictMock<MockProtoPointer> pointer2;
    testing::StrictMock<MockProtoAxisValue> axisValue1;
    testing::StrictMock<MockProtoAxisValue> axisValue2;
    testing::StrictMock<MockProtoAxisValue> axisValue3;
    testing::StrictMock<MockProtoAxisValue> axisValue4;
    testing::StrictMock<MockProtoAxisValue> axisValue5;
    testing::StrictMock<MockProtoAxisValue> axisValue6;

    EXPECT_CALL(proto, set_event_id(1));
    EXPECT_CALL(proto, set_event_time_nanos(2));
    EXPECT_CALL(proto, set_down_time_nanos(3));
    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
    EXPECT_CALL(proto, set_device_id(4));
    EXPECT_CALL(proto, set_display_id(5));
    EXPECT_CALL(proto, set_classification(AMOTION_EVENT_CLASSIFICATION_PINCH));
    EXPECT_CALL(proto, set_flags(ftl::Flags<MotionFlag>(MotionFlag::CANCELED).get()));
    EXPECT_CALL(proto, set_policy_flags(7));
    EXPECT_CALL(proto, set_button_state(8));
    EXPECT_CALL(proto, set_action_button(9));

    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));

    EXPECT_CALL(pointer1, set_pointer_id(15));
    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
    EXPECT_CALL(pointer1, add_axis_value())
            .WillOnce(Return(&axisValue1))
            .WillOnce(Return(&axisValue2))
            .WillOnce(Return(&axisValue3));
    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_PRESSURE));

    EXPECT_CALL(pointer2, set_pointer_id(16));
    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
    EXPECT_CALL(pointer2, add_axis_value())
            .WillOnce(Return(&axisValue4))
            .WillOnce(Return(&axisValue5))
            .WillOnce(Return(&axisValue6));
    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue5, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue6, set_axis(AMOTION_EVENT_AXIS_PRESSURE));

    // Redacted fields
    EXPECT_CALL(proto, set_meta_state(_)).Times(0);
    EXPECT_CALL(proto, set_cursor_position_x(_)).Times(0);
    EXPECT_CALL(proto, set_cursor_position_y(_)).Times(0);
    EXPECT_CALL(proto, set_precision_x(_)).Times(0);
    EXPECT_CALL(proto, set_precision_y(_)).Times(0);
    EXPECT_CALL(axisValue1, set_value(_)).Times(0);
    EXPECT_CALL(axisValue2, set_value(_)).Times(0);
    EXPECT_CALL(axisValue3, set_value(_)).Times(0);
    EXPECT_CALL(axisValue4, set_value(_)).Times(0);
    EXPECT_CALL(axisValue5, set_value(_)).Times(0);
    EXPECT_CALL(axisValue6, set_value(_)).Times(0);

    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/true);
}

// Test any special handling for zero values for pointer events.
TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent_ZeroValues) {
    TracedMotionEvent event{};
    event.id = 0;
    event.eventTime = 0;
    event.downTime = 0;
    event.source = AINPUT_SOURCE_MOUSE;
    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
    event.deviceId = 0;
    event.displayId = ui::LogicalDisplayId(0);
    event.classification = {};
    event.flags = {};
    event.policyFlags = 0;
    event.buttonState = 0;
    event.actionButton = 0;
    event.xCursorPosition = 0.0f;
    event.yCursorPosition = 0.0f;
    event.metaState = 0;
    event.xPrecision = 0.0f;
    event.yPrecision = 0.0f;
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 0,
            .toolType = ToolType::MOUSE,
    });
    event.pointerProperties.emplace_back(PointerProperties{
            .id = 1,
            .toolType = ToolType::FINGER,
    });
    // Zero values for x and y axes are always traced for pointer events.
    // However, zero values for other axes may not necessarily be traced.
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 0.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 1.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
    event.pointerCoords.emplace_back();
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 0.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 0.0f);
    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);

    testing::StrictMock<MockProtoMotion> proto;
    testing::StrictMock<MockProtoPointer> pointer1;
    testing::StrictMock<MockProtoPointer> pointer2;
    testing::StrictMock<MockProtoAxisValue> axisValue1;
    testing::StrictMock<MockProtoAxisValue> axisValue2;
    testing::StrictMock<MockProtoAxisValue> axisValue3;
    testing::StrictMock<MockProtoAxisValue> axisValue4;

    EXPECT_CALL(proto, set_event_id(0));
    EXPECT_CALL(proto, set_event_time_nanos(0));
    EXPECT_CALL(proto, set_down_time_nanos(0));
    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
    EXPECT_CALL(proto, set_device_id(0));
    EXPECT_CALL(proto, set_display_id(0));
    EXPECT_CALL(proto, set_classification(0));
    EXPECT_CALL(proto, set_flags(0));
    EXPECT_CALL(proto, set_policy_flags(0));
    EXPECT_CALL(proto, set_button_state(0));
    EXPECT_CALL(proto, set_action_button(0));
    EXPECT_CALL(proto, set_cursor_position_x(0.0f));
    EXPECT_CALL(proto, set_cursor_position_y(0.0f));
    EXPECT_CALL(proto, set_meta_state(0));
    EXPECT_CALL(proto, set_precision_x(0.0f));
    EXPECT_CALL(proto, set_precision_y(0.0f));

    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));

    EXPECT_CALL(pointer1, set_pointer_id(0));
    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
    EXPECT_CALL(pointer1, add_axis_value())
            .WillOnce(Return(&axisValue1))
            .WillOnce(Return(&axisValue2));
    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue1, set_value(0.0f));
    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue2, set_value(1.0f));

    EXPECT_CALL(pointer2, set_pointer_id(1));
    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
    EXPECT_CALL(pointer2, add_axis_value())
            .WillOnce(Return(&axisValue3))
            .WillOnce(Return(&axisValue4));
    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_X));
    EXPECT_CALL(axisValue3, set_value(0.0f));
    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue4, set_value(0.0f));

    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/false);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoKeyEvent) {
    TracedKeyEvent event{};
    event.id = 1;
    event.eventTime = 2;
    event.downTime = 3;
    event.source = AINPUT_SOURCE_KEYBOARD;
    event.action = AKEY_EVENT_ACTION_DOWN;
    event.deviceId = 4;
    event.displayId = ui::LogicalDisplayId(5);
    event.repeatCount = 6;
    event.flags = 7;
    event.policyFlags = 8;
    event.keyCode = 9;
    event.scanCode = 10;
    event.metaState = 11;

    testing::StrictMock<MockProtoKey> proto;

    EXPECT_CALL(proto, set_event_id(1));
    EXPECT_CALL(proto, set_event_time_nanos(2));
    EXPECT_CALL(proto, set_down_time_nanos(3));
    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_KEYBOARD));
    EXPECT_CALL(proto, set_action(AKEY_EVENT_ACTION_DOWN));
    EXPECT_CALL(proto, set_device_id(4));
    EXPECT_CALL(proto, set_display_id(5));
    EXPECT_CALL(proto, set_repeat_count(6));
    EXPECT_CALL(proto, set_flags(7));
    EXPECT_CALL(proto, set_policy_flags(8));
    EXPECT_CALL(proto, set_key_code(9));
    EXPECT_CALL(proto, set_scan_code(10));
    EXPECT_CALL(proto, set_meta_state(11));

    TestProtoConverter::toProtoKeyEvent(event, proto, /*isRedacted=*/false);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoKeyEvent_Redacted) {
    TracedKeyEvent event{};
    event.id = 1;
    event.eventTime = 2;
    event.downTime = 3;
    event.source = AINPUT_SOURCE_KEYBOARD;
    event.action = AKEY_EVENT_ACTION_DOWN;
    event.deviceId = 4;
    event.displayId = ui::LogicalDisplayId(5);
    event.repeatCount = 6;
    event.flags = 7;
    event.policyFlags = 8;
    event.keyCode = 9;
    event.scanCode = 10;
    event.metaState = 11;

    testing::StrictMock<MockProtoKey> proto;

    EXPECT_CALL(proto, set_event_id(1));
    EXPECT_CALL(proto, set_event_time_nanos(2));
    EXPECT_CALL(proto, set_down_time_nanos(3));
    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_KEYBOARD));
    EXPECT_CALL(proto, set_action(AKEY_EVENT_ACTION_DOWN));
    EXPECT_CALL(proto, set_device_id(4));
    EXPECT_CALL(proto, set_display_id(5));
    EXPECT_CALL(proto, set_repeat_count(6));
    EXPECT_CALL(proto, set_flags(7));
    EXPECT_CALL(proto, set_policy_flags(8));

    // Redacted fields
    EXPECT_CALL(proto, set_key_code(_)).Times(0);
    EXPECT_CALL(proto, set_scan_code(_)).Times(0);
    EXPECT_CALL(proto, set_meta_state(_)).Times(0);

    TestProtoConverter::toProtoKeyEvent(event, proto, /*isRedacted=*/true);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_IdentityTransform) {
    TracedMotionEvent motion{};
    motion.pointerProperties.emplace_back(PointerProperties{
            .id = 4,
            .toolType = ToolType::MOUSE,
    });
    motion.pointerCoords.emplace_back();
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 5.0f);
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);

    WindowDispatchArgs args{};
    args.eventEntry = motion;
    args.vsyncId = 1;
    args.windowId = 2;
    ftl::Flags<MotionFlag> resolvedFlags = MotionFlag::WINDOW_IS_OBSCURED;
    args.resolvedMotionFlags = resolvedFlags;
    args.rawTransform = ui::Transform{};
    args.transform = ui::Transform{};

    testing::StrictMock<MockProtoDispatch> proto;
    testing::StrictMock<MockProtoDispatchPointer> pointer;

    EXPECT_CALL(proto, set_event_id(0));
    EXPECT_CALL(proto, set_vsync_id(1));
    EXPECT_CALL(proto, set_window_id(2));
    EXPECT_CALL(proto, set_resolved_flags(resolvedFlags.get()));
    EXPECT_CALL(proto, add_dispatched_pointer()).WillOnce(Return(&pointer));
    EXPECT_CALL(pointer, set_pointer_id(4));

    // Since we are using identity transforms, the axis values will be identical to those in the
    // traced event, so they should not be traced here.
    EXPECT_CALL(pointer, add_axis_value_in_window()).Times(0);
    EXPECT_CALL(pointer, set_x_in_display(_)).Times(0);
    EXPECT_CALL(pointer, set_y_in_display(_)).Times(0);

    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/false);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_CustomTransform) {
    TracedMotionEvent motion{};
    motion.pointerProperties.emplace_back(PointerProperties{
            .id = 4,
            .toolType = ToolType::MOUSE,
    });
    motion.pointerCoords.emplace_back();
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 8.0f);
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);

    WindowDispatchArgs args{};
    args.eventEntry = motion;
    args.vsyncId = 1;
    args.windowId = 2;
    ftl::Flags<MotionFlag> resolvedFlags = MotionFlag::WINDOW_IS_OBSCURED;
    args.resolvedMotionFlags = resolvedFlags;
    args.rawTransform.set(2, 0, 0, 0.5);
    args.transform.set(1.0, 0, 0, 0.5);

    testing::StrictMock<MockProtoDispatch> proto;
    testing::StrictMock<MockProtoDispatchPointer> pointer;
    testing::StrictMock<MockProtoAxisValue> axisValue1;

    EXPECT_CALL(proto, set_event_id(0));
    EXPECT_CALL(proto, set_vsync_id(1));
    EXPECT_CALL(proto, set_window_id(2));
    EXPECT_CALL(proto, set_resolved_flags(resolvedFlags.get()));
    EXPECT_CALL(proto, add_dispatched_pointer()).WillOnce(Return(&pointer));
    EXPECT_CALL(pointer, set_pointer_id(4));

    // Only the transformed axis-values that differ from the traced event will be traced.
    EXPECT_CALL(pointer, add_axis_value_in_window()).WillOnce(Return(&axisValue1));
    EXPECT_CALL(pointer, set_x_in_display(16.0f)); // MotionEvent::getRawX
    EXPECT_CALL(pointer, set_y_in_display(3.0f));  // MotionEvent::getRawY

    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_Y));
    EXPECT_CALL(axisValue1, set_value(3.0f));

    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/false);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_Redacted) {
    TracedMotionEvent motion{};
    motion.pointerProperties.emplace_back(PointerProperties{
            .id = 4,
            .toolType = ToolType::MOUSE,
    });
    motion.pointerCoords.emplace_back();
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 5.0f);
    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);

    WindowDispatchArgs args{};
    args.eventEntry = motion;
    args.vsyncId = 1;
    args.windowId = 2;
    ftl::Flags<MotionFlag> resolvedFlags = MotionFlag::WINDOW_IS_OBSCURED;
    args.resolvedMotionFlags = resolvedFlags;
    args.rawTransform = ui::Transform{};
    args.transform = ui::Transform{};

    testing::StrictMock<MockProtoDispatch> proto;

    EXPECT_CALL(proto, set_event_id(0));
    EXPECT_CALL(proto, set_vsync_id(1));
    EXPECT_CALL(proto, set_window_id(2));
    EXPECT_CALL(proto, set_resolved_flags(resolvedFlags.get()));

    // Redacted fields
    EXPECT_CALL(proto, add_dispatched_pointer()).Times(0);

    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/true);
}

TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Key) {
    TracedKeyEvent key{};

    WindowDispatchArgs args{};
    args.eventEntry = key;
    args.vsyncId = 1;
    args.windowId = 2;
    args.resolvedMotionFlags = MotionFlag::WINDOW_IS_OBSCURED;
    args.rawTransform = ui::Transform{};
    args.transform = ui::Transform{};

    testing::StrictMock<MockProtoDispatch> proto;

    EXPECT_CALL(proto, set_event_id(0));
    EXPECT_CALL(proto, set_vsync_id(1));
    EXPECT_CALL(proto, set_window_id(2));
    EXPECT_CALL(proto,
                set_resolved_flags(ftl::Flags<MotionFlag>(MotionFlag::WINDOW_IS_OBSCURED).get()));

    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/true);
}

} // namespace

} // namespace android::inputdispatcher::trace
