chromium/components/exo/gamepad.cc

// Copyright 2020 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/gamepad.h"

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/logging.h"

namespace exo {

Gamepad::Gamepad(const ui::GamepadDevice& gamepad_device)
    : device(gamepad_device),
      input_controller_(
          ui::OzonePlatform::GetInstance()->GetInputController()) {}

Gamepad::~Gamepad() {
  for (GamepadObserver& observer : observer_list_)
    observer.OnGamepadDestroying(this);

  if (delegate_)
    delegate_->OnRemoved();
}

void Gamepad::Vibrate(const std::vector<int64_t>& duration_millis,
                      const std::vector<uint8_t>& amplitudes,
                      int32_t repeat) {
  if (!device.supports_vibration_rumble) {
    VLOG(2) << "Vibrate failed because gamepad does not support vibration.";
    return;
  }

  if (duration_millis.size() != amplitudes.size()) {
    VLOG(2) << "Vibrate failed because the amplitudes vector and "
               "duration_millis vector are not the same size.";
    return;
  }

  vibration_timer_.Stop();
  vibration_timer_.Start(
      FROM_HERE, base::Milliseconds(0),
      base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this),
                     duration_millis, amplitudes, repeat, /*index=*/0,
                     /*duration_already_vibrated=*/0));
}

void Gamepad::HandleVibrate(const std::vector<int64_t>& duration_millis,
                            const std::vector<uint8_t>& amplitudes,
                            int32_t repeat,
                            size_t index,
                            int64_t duration_already_vibrated) {
  size_t vector_size = duration_millis.size();
  if (index >= vector_size)
    return;

  if (!can_vibrate_) {
    VLOG(2) << "Gamepad is not allowed to vibrate because it is not in focus.";
    return;
  }

  int64_t duration_left_to_vibrate =
      duration_millis[index] - duration_already_vibrated;

  if (duration_left_to_vibrate > kMaxDurationMillis) {
    //  The device does not support effects this long. Issue periodic vibration
    //  commands until the effect is complete.
    SendVibrate(amplitudes[index], kMaxDurationMillis);
    vibration_timer_.Start(
        FROM_HERE, base::Milliseconds(kMaxDurationMillis),
        base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this),
                       duration_millis, amplitudes, repeat, index,
                       /*duration_already_vibrated=*/duration_already_vibrated +
                           kMaxDurationMillis));
  } else {
    SendVibrate(amplitudes[index], duration_left_to_vibrate);
    index++;
    bool needs_to_repeat = index >= vector_size && repeat >= 0 &&
                           repeat < static_cast<int32_t>(vector_size);
    if (needs_to_repeat)
      index = repeat;

    vibration_timer_.Start(
        FROM_HERE, base::Milliseconds(duration_left_to_vibrate),
        base::BindOnce(&Gamepad::HandleVibrate, base::Unretained(this),
                       duration_millis, amplitudes, repeat, index,
                       /*duration_already_vibrated=*/0));
  }
}

void Gamepad::SendVibrate(uint8_t amplitude, int64_t duration_millis) {
  // |duration_millis| is always <= |kMaxDurationMillis|, which is the max value
  // for uint16_t, so it is safe to cast it to uint16_t here.
  input_controller_->PlayVibrationEffect(
      device.id, amplitude, static_cast<uint16_t>(duration_millis));
}

void Gamepad::CancelVibration() {
  if (!device.supports_vibration_rumble) {
    VLOG(2)
        << "CancelVibration failed because gamepad does not support vibration.";
    return;
  }

  if (!vibration_timer_.IsRunning())
    return;

  vibration_timer_.Stop();
  SendCancelVibration();
}

void Gamepad::SendCancelVibration() {
  input_controller_->StopVibration(device.id);
}

void Gamepad::SetDelegate(std::unique_ptr<GamepadDelegate> delegate) {
  DCHECK(!delegate_);
  delegate_ = std::move(delegate);
}

void Gamepad::AddObserver(GamepadObserver* observer) {
  observer_list_.AddObserver(observer);
}

bool Gamepad::HasObserver(GamepadObserver* observer) const {
  return observer_list_.HasObserver(observer);
}

void Gamepad::RemoveObserver(GamepadObserver* observer) {
  observer_list_.RemoveObserver(observer);
}

void Gamepad::OnGamepadFocused() {
  can_vibrate_ = base::FeatureList::IsEnabled(ash::features::kGamepadVibration);
}

void Gamepad::OnGamepadFocusLost() {
  can_vibrate_ = false;
  CancelVibration();
}

void Gamepad::OnGamepadEvent(const ui::GamepadEvent& event) {
  DCHECK(delegate_);
  switch (event.type()) {
    case ui::GamepadEventType::BUTTON:
      delegate_->OnButton(event.code(), event.value(), event.timestamp());
      break;
    case ui::GamepadEventType::AXIS:
      delegate_->OnAxis(event.code(), event.value(), event.timestamp());
      break;
    case ui::GamepadEventType::FRAME:
      delegate_->OnFrame(event.timestamp());
      break;
  }
}

}  // namespace exo