#include "device/gamepad/gamepad_provider.h"
#include <memory>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/gamepad_test_helpers.h"
#include "device/gamepad/public/cpp/gamepad_features.h"
#include "device/gamepad/public/cpp/gamepad_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
constexpr int64_t kInitialTimeStamp = 0;
constexpr int64_t kUserGestureTimeStamp = 1;
constexpr int64_t kPollTimeStamp = 2;
class UserGestureListener {
public:
UserGestureListener() : has_user_gesture_(false) {}
base::OnceClosure GetClosure() {
return base::BindOnce(&UserGestureListener::GotUserGesture,
weak_factory_.GetWeakPtr());
}
void WaitForUserGesture() {
if (has_user_gesture_) {
return;
}
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
bool has_user_gesture() const { return has_user_gesture_; }
private:
void GotUserGesture() {
has_user_gesture_ = true;
if (quit_closure_) {
std::move(quit_closure_).Run();
}
}
bool has_user_gesture_;
base::OnceClosure quit_closure_;
base::WeakPtrFactory<UserGestureListener> weak_factory_{this};
};
class GamepadProviderTest : public testing::Test, public GamepadTestHelper {
public:
GamepadProviderTest(const GamepadProviderTest&) = delete;
GamepadProviderTest& operator=(const GamepadProviderTest&) = delete;
GamepadProvider* CreateProvider(const Gamepads& test_data) {
auto fetcher = std::make_unique<MockGamepadDataFetcher>(test_data);
mock_data_fetcher_ = fetcher.get();
provider_ = std::make_unique<GamepadProvider>(
nullptr, std::move(fetcher),
nullptr);
return provider_.get();
}
void WaitForData(const GamepadHardwareBuffer* buffer) {
const int32_t initial_version = buffer->seqlock.ReadBegin();
int32_t current_version;
do {
base::PlatformThread::Sleep(base::Milliseconds(10));
current_version = buffer->seqlock.ReadBegin();
} while (current_version % 2 || current_version == initial_version);
}
void WaitForDataAndCallbacksIssued(const GamepadHardwareBuffer* buffer) {
WaitForData(buffer);
WaitForData(buffer);
}
void ReadGamepadHardwareBuffer(const GamepadHardwareBuffer* buffer,
Gamepads* output) {
UNSAFE_TODO(memset(output, 0, sizeof(Gamepads)));
int32_t version;
do {
version = buffer->seqlock.ReadBegin();
UNSAFE_TODO(memcpy(output, &buffer->data, sizeof(Gamepads)));
} while (buffer->seqlock.ReadRetry(version));
}
void EnableRawInputChangeDetection() {
scoped_feature_list_.InitAndEnableFeature(
features::kGamepadRawInputChangeEvent);
}
void SetInitialStateForRawInputChange(Gamepads& data, int64_t timestamp) {
data.items[0] = {};
data.items[0].connected = true;
data.items[0].timestamp = timestamp;
data.items[0].buttons_length = 4;
data.items[0].axes_length = 2;
data.items[0].touch_events_length = 2;
data.items[0].supports_touch_events_ = true;
data.items[0].touch_events[0].touch_id = 1;
data.items[0].touch_events[1].touch_id = 2;
}
void SetUserGestureForRawInputChange(Gamepads& data, int64_t timestamp) {
data.items[0].timestamp = timestamp;
data.items[0].buttons[0].value = 1.0f;
data.items[0].buttons[0].pressed = true;
}
void TestRawInputChangeDetection(
const std::string& test_name,
base::OnceCallback<void(Gamepads&)> setup_input_change_data,
base::OnceCallback<void(const Gamepads&)> verify_final_state) {
Gamepads test_data = {};
SetInitialStateForRawInputChange(test_data, kInitialTimeStamp);
GamepadProvider* provider = CreateProvider(test_data);
provider->SetSanitizationEnabled(false);
provider->Resume();
UserGestureListener listener;
provider->RegisterForUserGesture(listener.GetClosure());
base::ReadOnlySharedMemoryRegion region =
provider->DuplicateSharedMemoryRegion();
base::ReadOnlySharedMemoryMapping mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
const GamepadHardwareBuffer* buffer =
static_cast<const GamepadHardwareBuffer*>(mapping.memory());
WaitForData(buffer);
EXPECT_FALSE(provider->HasInputChangedForTesting())
<< "Initial state should show no input change for " << test_name;
Gamepads user_gesture_data = test_data;
SetUserGestureForRawInputChange(user_gesture_data, kUserGestureTimeStamp);
mock_data_fetcher_->SetTestData(user_gesture_data);
WaitForDataAndCallbacksIssued(buffer);
listener.WaitForUserGesture();
EXPECT_TRUE(listener.has_user_gesture())
<< "User gesture should be detected for " << test_name;
Gamepads input_change_data = user_gesture_data;
input_change_data.items[0].timestamp = kPollTimeStamp;
std::move(setup_input_change_data).Run(input_change_data);
mock_data_fetcher_->SetTestData(input_change_data);
WaitForDataAndCallbacksIssued(buffer);
Gamepads output;
ReadGamepadHardwareBuffer(buffer, &output);
std::move(verify_final_state).Run(output);
if (user_gesture_data.items[0].timestamp !=
input_change_data.items[0].timestamp) {
EXPECT_TRUE(provider->HasInputChangedForTesting())
<< "Input change should be detected for " << test_name;
} else {
EXPECT_FALSE(provider->HasInputChangedForTesting())
<< "Input change should NOT be detected for " << test_name;
}
}
protected:
GamepadProviderTest() = default;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<GamepadProvider> provider_;
raw_ptr<MockGamepadDataFetcher> mock_data_fetcher_;
};
TEST_F(GamepadProviderTest, PollingAccess) {
Gamepads test_data;
UNSAFE_TODO(memset(&test_data, 0, sizeof(Gamepads)));
test_data.items[0].connected = true;
test_data.items[0].timestamp = 0;
test_data.items[0].buttons_length = 1;
test_data.items[0].axes_length = 2;
test_data.items[0].buttons[0].value = 1.0f;
test_data.items[0].buttons[0].pressed = true;
test_data.items[0].axes[0] = -1.0f;
test_data.items[0].axes[1] = 0.5f;
GamepadProvider* provider = CreateProvider(test_data);
provider->SetSanitizationEnabled(false);
provider->Resume();
base::RunLoop().RunUntilIdle();
base::ReadOnlySharedMemoryRegion region =
provider->DuplicateSharedMemoryRegion();
base::ReadOnlySharedMemoryMapping mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
const GamepadHardwareBuffer* buffer =
static_cast<const GamepadHardwareBuffer*>(mapping.memory());
WaitForData(buffer);
Gamepads output;
ReadGamepadHardwareBuffer(buffer, &output);
ASSERT_EQ(1u, output.items[0].buttons_length);
EXPECT_EQ(1.0f, output.items[0].buttons[0].value);
EXPECT_EQ(true, output.items[0].buttons[0].pressed);
ASSERT_EQ(2u, output.items[0].axes_length);
EXPECT_EQ(-1.0f, output.items[0].axes[0]);
EXPECT_EQ(0.5f, output.items[0].axes[1]);
}
TEST_F(GamepadProviderTest, ConnectDisconnectMultiple) {
Gamepads test_data;
test_data.items[0].connected = true;
test_data.items[0].timestamp = 0;
test_data.items[0].axes_length = 2;
test_data.items[0].axes[0] = -1.0f;
test_data.items[0].axes[1] = 0.5f;
test_data.items[1].connected = true;
test_data.items[1].timestamp = 0;
test_data.items[1].axes_length = 2;
test_data.items[1].axes[0] = 1.0f;
test_data.items[1].axes[1] = -0.5f;
Gamepads test_data_onedisconnected;
test_data_onedisconnected.items[1].connected = true;
test_data_onedisconnected.items[1].timestamp = 0;
test_data_onedisconnected.items[1].axes_length = 2;
test_data_onedisconnected.items[1].axes[0] = 1.0f;
test_data_onedisconnected.items[1].axes[1] = -0.5f;
GamepadProvider* provider = CreateProvider(test_data);
provider->SetSanitizationEnabled(false);
provider->Resume();
base::RunLoop().RunUntilIdle();
base::ReadOnlySharedMemoryRegion region =
provider->DuplicateSharedMemoryRegion();
base::ReadOnlySharedMemoryMapping mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
const GamepadHardwareBuffer* buffer =
static_cast<const GamepadHardwareBuffer*>(mapping.memory());
WaitForData(buffer);
Gamepads output;
ReadGamepadHardwareBuffer(buffer, &output);
ASSERT_EQ(2u, output.items[0].axes_length);
EXPECT_EQ(-1.0f, output.items[0].axes[0]);
EXPECT_EQ(0.5f, output.items[0].axes[1]);
ASSERT_EQ(2u, output.items[1].axes_length);
EXPECT_EQ(1.0f, output.items[1].axes[0]);
EXPECT_EQ(-0.5f, output.items[1].axes[1]);
mock_data_fetcher_->SetTestData(test_data_onedisconnected);
WaitForDataAndCallbacksIssued(buffer);
ReadGamepadHardwareBuffer(buffer, &output);
EXPECT_EQ(0u, output.items[0].axes_length);
ASSERT_EQ(2u, output.items[1].axes_length);
EXPECT_EQ(1.0f, output.items[1].axes[0]);
EXPECT_EQ(-0.5f, output.items[1].axes[1]);
}
TEST_F(GamepadProviderTest, UserGesture) {
Gamepads no_button_data;
no_button_data.items[0].connected = true;
no_button_data.items[0].timestamp = 0;
no_button_data.items[0].buttons_length = 1;
no_button_data.items[0].axes_length = 2;
no_button_data.items[0].buttons[0].value = 0.0f;
no_button_data.items[0].buttons[0].pressed = false;
no_button_data.items[0].axes[0] = 0.0f;
no_button_data.items[0].axes[1] = 0.4f;
Gamepads button_down_data = no_button_data;
button_down_data.items[0].buttons[0].value = 1.0f;
button_down_data.items[0].buttons[0].pressed = true;
UserGestureListener listener;
GamepadProvider* provider = CreateProvider(no_button_data);
provider->SetSanitizationEnabled(false);
provider->Resume();
provider->RegisterForUserGesture(listener.GetClosure());
base::RunLoop().RunUntilIdle();
base::ReadOnlySharedMemoryRegion region =
provider->DuplicateSharedMemoryRegion();
base::ReadOnlySharedMemoryMapping mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
const GamepadHardwareBuffer* buffer =
static_cast<const GamepadHardwareBuffer*>(mapping.memory());
WaitForData(buffer);
EXPECT_FALSE(listener.has_user_gesture());
mock_data_fetcher_->SetTestData(button_down_data);
WaitForDataAndCallbacksIssued(buffer);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(listener.has_user_gesture());
}
TEST_F(GamepadProviderTest, Sanitization) {
Gamepads active_data;
active_data.items[0].connected = true;
active_data.items[0].timestamp = 0;
active_data.items[0].buttons_length = 1;
active_data.items[0].axes_length = 1;
active_data.items[0].buttons[0].value = 1.0f;
active_data.items[0].buttons[0].pressed = true;
active_data.items[0].axes[0] = -1.0f;
Gamepads zero_data;
zero_data.items[0].connected = true;
zero_data.items[0].timestamp = 0;
zero_data.items[0].buttons_length = 1;
zero_data.items[0].axes_length = 1;
zero_data.items[0].buttons[0].value = 0.0f;
zero_data.items[0].buttons[0].pressed = false;
zero_data.items[0].axes[0] = 0.0f;
UserGestureListener listener;
GamepadProvider* provider = CreateProvider(active_data);
provider->SetSanitizationEnabled(true);
provider->Resume();
base::RunLoop().RunUntilIdle();
base::ReadOnlySharedMemoryRegion region =
provider->DuplicateSharedMemoryRegion();
base::ReadOnlySharedMemoryMapping mapping = region.Map();
ASSERT_TRUE(mapping.IsValid());
const GamepadHardwareBuffer* buffer =
static_cast<const GamepadHardwareBuffer*>(mapping.memory());
WaitForData(buffer);
Gamepads output;
ReadGamepadHardwareBuffer(buffer, &output);
ASSERT_EQ(1u, output.items[0].buttons_length);
EXPECT_EQ(0.0f, output.items[0].buttons[0].value);
EXPECT_FALSE(output.items[0].buttons[0].pressed);
ASSERT_EQ(1u, output.items[0].axes_length);
EXPECT_EQ(0.0f, output.items[0].axes[0]);
mock_data_fetcher_->SetTestData(zero_data);
WaitForDataAndCallbacksIssued(buffer);
ReadGamepadHardwareBuffer(buffer, &output);
ASSERT_EQ(1u, output.items[0].buttons_length);
EXPECT_EQ(0.0f, output.items[0].buttons[0].value);
EXPECT_FALSE(output.items[0].buttons[0].pressed);
ASSERT_EQ(1u, output.items[0].axes_length);
EXPECT_EQ(0.0f, output.items[0].axes[0]);
mock_data_fetcher_->SetTestData(active_data);
WaitForDataAndCallbacksIssued(buffer);
ReadGamepadHardwareBuffer(buffer, &output);
ASSERT_EQ(1u, output.items[0].buttons_length);
EXPECT_EQ(1.0f, output.items[0].buttons[0].value);
EXPECT_TRUE(output.items[0].buttons[0].pressed);
ASSERT_EQ(1u, output.items[0].axes_length);
EXPECT_EQ(-1.0f, output.items[0].axes[0]);
}
TEST_F(GamepadProviderTest, HasInputChangedDetectsButtonChanges) {
EnableRawInputChangeDetection();
TestRawInputChangeDetection(
"button changes",
base::BindOnce([](Gamepads& data) {
data.items[0].buttons[0].value = 1.0f;
data.items[0].buttons[0].pressed = false;
}),
base::BindOnce([](const Gamepads& output) {
EXPECT_EQ(1.0f, output.items[0].buttons[0].value);
EXPECT_EQ(false, output.items[0].buttons[0].pressed);
}));
}
TEST_F(GamepadProviderTest, HasInputChangedDetectsAxisChanges) {
EnableRawInputChangeDetection();
TestRawInputChangeDetection("axis changes",
base::BindOnce([](Gamepads& data) {
data.items[0].axes[0] = -1.0f;
data.items[0].axes[1] = 0.3f;
}),
base::BindOnce([](const Gamepads& output) {
EXPECT_EQ(-1.0f, output.items[0].axes[0]);
EXPECT_EQ(0.3f, output.items[0].axes[1]);
}));
}
TEST_F(GamepadProviderTest, HasInputChangedDetectsTouchChanges) {
EnableRawInputChangeDetection();
TestRawInputChangeDetection(
"touch changes",
base::BindOnce([](Gamepads& data) {
data.items[0].touch_events[0].x = -0.2f;
data.items[0].touch_events[0].y = 0.8f;
data.items[0].touch_events[1].x = 1.0f;
data.items[0].touch_events[1].y = -0.4f;
}),
base::BindOnce([](const Gamepads& output) {
EXPECT_EQ(-0.2f, output.items[0].touch_events[0].x);
EXPECT_EQ(0.8f, output.items[0].touch_events[0].y);
EXPECT_EQ(1.0f, output.items[0].touch_events[1].x);
EXPECT_EQ(-0.4f, output.items[0].touch_events[1].y);
}));
}
TEST_F(GamepadProviderTest, NoInputChangeDetectedWithUnchangedTimestamp) {
EnableRawInputChangeDetection();
TestRawInputChangeDetection(
"unchanged timestamp",
base::BindOnce([](Gamepads& data) {
data.items[0].timestamp = kUserGestureTimeStamp;
data.items[0].buttons[0].value = 0.5f;
}),
base::BindOnce([](const Gamepads& output) {
EXPECT_EQ(0.5f, output.items[0].buttons[0].value);
}));
}
}
}