chromium/components/exo/gaming_seat_unittest.cc

// Copyright 2015 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/exo/gaming_seat.h"

#include <memory>
#include <vector>

#include "ash/shell.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "components/exo/buffer.h"
#include "components/exo/gamepad.h"
#include "components/exo/gamepad_delegate.h"
#include "components/exo/gaming_seat_delegate.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "device/gamepad/gamepad_test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/focus_client.h"
#include "ui/events/ozone/gamepad/gamepad_provider_ozone.h"

namespace exo {
namespace {

class MockGamepadDelegate : public GamepadDelegate {
 public:
  MockGamepadDelegate() {}

  // Overridden from GamepadDelegate:
  MOCK_METHOD(void, OnRemoved, (), (override));
  MOCK_METHOD(void,
              OnAxis,
              (int axis, double value, base::TimeTicks timestamp),
              (override));
  MOCK_METHOD(void,
              OnButton,
              (int button, bool pressed, base::TimeTicks timestamp),
              (override));
  MOCK_METHOD(void, OnFrame, (base::TimeTicks timestamp), (override));
};

class MockGamingSeatDelegate : public GamingSeatDelegate {
 public:
  MOCK_METHOD(bool,
              CanAcceptGamepadEventsForSurface,
              (Surface * surface),
              (const, override));
  MOCK_METHOD(void, GamepadAdded, (Gamepad & gamepad), (override));
  MOCK_METHOD(void, Die, (), ());
  void OnGamingSeatDestroying(GamingSeat*) override { delete this; }
  ~MockGamingSeatDelegate() override { Die(); }
};

class GamingSeatTest : public test::ExoTestBase {
 public:
  GamingSeatTest() {}

  GamingSeatTest(const GamingSeatTest&) = delete;
  GamingSeatTest& operator=(const GamingSeatTest&) = delete;

  void InitializeGamingSeat(MockGamingSeatDelegate* delegate) {
    gaming_seat_ = std::make_unique<GamingSeat>(delegate);
  }

  void DestroyGamingSeat(MockGamingSeatDelegate* delegate) {
    EXPECT_CALL(*delegate, Die()).Times(1);
    gaming_seat_.reset();
  }

  void UpdateGamepadDevice(const std::vector<int>& gamepad_device_ids) {
    std::vector<ui::GamepadDevice> gamepad_devices;
    for (auto& id : gamepad_device_ids) {
      gamepad_devices.emplace_back(
          ui::InputDevice(id, ui::InputDeviceType::INPUT_DEVICE_USB, "gamepad"),
          std::vector<ui::GamepadDevice::Axis>(),
          /*supports_vibration_rumble=*/false);
    }
    ui::GamepadProviderOzone::GetInstance()->DispatchGamepadDevicesUpdated(
        gamepad_devices);
  }

  void SendFrameToGamepads(const std::vector<int>& gamepad_device_ids) {
    for (auto& id : gamepad_device_ids) {
      ui::GamepadEvent event(id, ui::GamepadEventType::FRAME, 0, 0,
                             base::TimeTicks());
      ui::GamepadProviderOzone::GetInstance()->DispatchGamepadEvent(event);
    }
  }

  void SendButtonToGamepads(const std::vector<int>& gamepad_device_ids,
                            base::TimeTicks timestamp) {
    for (auto& id : gamepad_device_ids) {
      ui::GamepadEvent event(id, ui::GamepadEventType::BUTTON, 310, 1,
                             timestamp);
      ui::GamepadProviderOzone::GetInstance()->DispatchGamepadEvent(event);
    }
  }

 protected:
  std::unique_ptr<GamingSeat> gaming_seat_;
};

TEST_F(GamingSeatTest, ConnectionChange) {
  std::unique_ptr<Surface> surface(new Surface);
  std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
  gfx::Size buffer_size(10, 10);
  auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size);
  surface->Attach(buffer.get());
  surface->Commit();

  testing::StrictMock<MockGamingSeatDelegate>* gaming_seat_delegate =
      new testing::StrictMock<MockGamingSeatDelegate>();
  EXPECT_CALL(*gaming_seat_delegate,
              CanAcceptGamepadEventsForSurface(testing::_))
      .WillOnce(testing::Return(true));

  InitializeGamingSeat(gaming_seat_delegate);
  std::unique_ptr<MockGamepadDelegate> gamepad_delegates[6];
  for (auto& delegate : gamepad_delegates)
    delegate = std::make_unique<testing::StrictMock<MockGamepadDelegate>>();

  {  // Test sequence
    testing::InSequence s;
    // Connect 2 gamepads.
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[0]));
        }));
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[1]));
        }));
    // Send frame to connected gamepad.
    EXPECT_CALL(*gamepad_delegates[0], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[1], OnFrame(testing::_)).Times(1);
    // Connect 3 more.
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[2]));
        }));
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[3]));
        }));
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[4]));
        }));
    // Send frame to all gamepads.
    EXPECT_CALL(*gamepad_delegates[0], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[1], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[2], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[3], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[4], OnFrame(testing::_)).Times(1);
    // Disconnect gamepads 0, 2 and 4.
    EXPECT_CALL(*gamepad_delegates[0], OnRemoved()).Times(1);
    EXPECT_CALL(*gamepad_delegates[2], OnRemoved()).Times(1);
    EXPECT_CALL(*gamepad_delegates[4], OnRemoved()).Times(1);
    // Connect a new gamepad.
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegates](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegates[5]));
        }));
    // Send frame to all gamepads.
    EXPECT_CALL(*gamepad_delegates[1], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[3], OnFrame(testing::_)).Times(1);
    EXPECT_CALL(*gamepad_delegates[5], OnFrame(testing::_)).Times(1);
  }

  // The rest of gamepads should be disconnected after GamingSeat is
  // destroyed.
  EXPECT_CALL(*gamepad_delegates[1], OnRemoved()).Times(1);
  EXPECT_CALL(*gamepad_delegates[3], OnRemoved()).Times(1);
  EXPECT_CALL(*gamepad_delegates[5], OnRemoved()).Times(1);

  // Gamepad connected.
  UpdateGamepadDevice({0, 1});
  SendFrameToGamepads({0, 1});
  UpdateGamepadDevice({0, 1, 2, 3, 4});
  SendFrameToGamepads({0, 1, 2, 3, 4});
  UpdateGamepadDevice({1, 2, 3, 4});
  UpdateGamepadDevice({1, 3, 4});
  UpdateGamepadDevice({1, 3});
  UpdateGamepadDevice({1, 3, 5});
  SendFrameToGamepads({1, 2, 3, 4, 5});
  DestroyGamingSeat(gaming_seat_delegate);
  UpdateGamepadDevice({});
}

TEST_F(GamingSeatTest, Timestamp) {
  std::unique_ptr<Surface> surface(new Surface);
  std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
  gfx::Size buffer_size(10, 10);
  auto buffer = test::ExoTestHelper::CreateBuffer(buffer_size);
  surface->Attach(buffer.get());
  surface->Commit();

  testing::StrictMock<MockGamingSeatDelegate>* gaming_seat_delegate =
      new testing::StrictMock<MockGamingSeatDelegate>();
  EXPECT_CALL(*gaming_seat_delegate,
              CanAcceptGamepadEventsForSurface(testing::_))
      .WillOnce(testing::Return(true));

  InitializeGamingSeat(gaming_seat_delegate);
  auto gamepad_delegate =
      std::make_unique<testing::StrictMock<MockGamepadDelegate>>();
  base::TimeTicks expected_time = base::TimeTicks::Now();

  {  // Test sequence
    testing::InSequence s;

    // Connect gamepad.
    EXPECT_CALL(*gaming_seat_delegate, GamepadAdded(testing::_))
        .WillOnce(testing::Invoke([&gamepad_delegate](auto& gamepad) {
          gamepad.SetDelegate(std::move(gamepad_delegate));
        }));
    // Send button to connected gamepad. Expect correct timestamp.
    EXPECT_CALL(*gamepad_delegate,
                OnButton(testing::_, testing::_, testing::Eq(expected_time)))
        .Times(1);
  }

  // Disconnect gamepad.
  EXPECT_CALL(*gamepad_delegate, OnRemoved()).Times(1);

  // Gamepad connected.
  UpdateGamepadDevice({1});
  SendButtonToGamepads({1}, expected_time);
  UpdateGamepadDevice({});
  DestroyGamingSeat(gaming_seat_delegate);
}

}  // namespace
}  // namespace exo