chromium/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ash/components/dbus/rgbkbd/fake_rgbkbd_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/rgbkbd/dbus-constants.h"

namespace ash {

namespace {

RgbkbdClient* g_instance = nullptr;

class RgbkbdClientImpl : public RgbkbdClient {
 public:
  void Init(dbus::Bus* bus);
  RgbkbdClientImpl() = default;
  RgbkbdClientImpl(const RgbkbdClientImpl&) = delete;
  RgbkbdClientImpl& operator=(const RgbkbdClientImpl&) = delete;

  void GetRgbKeyboardCapabilities(
      GetRgbKeyboardCapabilitiesCallback callback) override {
    VLOG(1) << "rgbkbd: GetRgbKeyboardCapabilities called";
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kGetRgbKeyboardCapabilities);
    dbus::MessageWriter writer(&method_call);
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&RgbkbdClientImpl::GetRgbKeyboardCapabilitiesCallback,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetCapsLockState(bool enabled) override {
    VLOG(1) << "rgbkbd: SetCapsLockState called with: "
            << (enabled ? "True" : "False");
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kSetCapsLockState);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(enabled);
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(&method_call,
                              dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                              base::DoNothing());
  }

  void SetStaticBackgroundColor(uint8_t r, uint8_t g, uint8_t b) override {
    VLOG(1) << "rgbkbd: SetStaticBackgroundColor  R: " << static_cast<int>(r)
            << "G: " << static_cast<int>(g) << "B: " << static_cast<int>(b);
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kSetStaticBackgroundColor);
    dbus::MessageWriter writer(&method_call);
    writer.AppendByte(r);
    writer.AppendByte(g);
    writer.AppendByte(b);
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(&method_call,
                              dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                              base::DoNothing());
  }

  void SetZoneColor(int zone, uint8_t r, uint8_t g, uint8_t b) override {
    VLOG(1) << "rgbkbd: SetZoneColor Zone: " << zone
            << "R: " << static_cast<int>(r) << "G: " << static_cast<int>(g)
            << "B: " << static_cast<int>(b);
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kSetZoneColor);
    dbus::MessageWriter writer(&method_call);
    writer.AppendInt32(zone);
    writer.AppendByte(r);
    writer.AppendByte(g);
    writer.AppendByte(b);
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(&method_call,
                              dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                              base::DoNothing());
  }

  void SetRainbowMode() override {
    VLOG(1) << "rgbkbd: SetRainbowMode";
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kSetRainbowMode);
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(&method_call,
                              dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                              base::DoNothing());
  }

  void SetAnimationMode(rgbkbd::RgbAnimationMode mode) override {
    VLOG(1) << "rgbkbd: SetAnimationMode with mode: "
            << static_cast<uint32_t>(mode);
    dbus::MethodCall method_call(rgbkbd::kRgbkbdServiceName,
                                 rgbkbd::kSetAnimationMode);
    dbus::MessageWriter writer(&method_call);
    writer.AppendUint32(static_cast<uint32_t>(mode));
    CHECK(rgbkbd_proxy_);
    rgbkbd_proxy_->CallMethod(&method_call,
                              dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                              base::DoNothing());
  }

 private:
  void GetRgbKeyboardCapabilitiesCallback(
      GetRgbKeyboardCapabilitiesCallback callback,
      dbus::Response* response) {
    if (!response) {
      VLOG(1)
          << "rgbkbd: No Dbus response received for GetRgbKeyboardCapabilities";
      std::move(callback).Run(std::nullopt);
      return;
    }
    dbus::MessageReader reader(response);
    uint32_t keyboard_capabilities;

    if (!reader.PopUint32(&keyboard_capabilities)) {
      LOG(ERROR)
          << "rgbkbd: Error reading GetRgbKeyboardCapabilities response: "
          << response->ToString();
      std::move(callback).Run(std::nullopt);
      return;
    }
    VLOG(1) << "rgbkbd: Value for keyboard capabilities is: "
            << keyboard_capabilities;
    std::move(callback).Run(
        rgbkbd::RgbKeyboardCapabilities(keyboard_capabilities));
  }

  void CapabilityUpdatedForTestingReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    uint32_t capability;

    if (!reader.PopUint32(&capability)) {
      LOG(ERROR) << "rgbkbd: Error reading capability for testing response: "
                 << signal->ToString();
      return;
    }
    for (auto& observer : observers_) {
      observer.OnCapabilityUpdatedForTesting(  // IN-TEST
          rgbkbd::RgbKeyboardCapabilities(capability));
    }
  }

  void CapabilityUpdatedForTestingConnected(const std::string& interface_name,
                                            const std::string& signal_name,
                                            bool success) {
    LOG_IF(WARNING, !success)
        << "Failed to connect to CapabilityUpdatedForTesting signal.";
  }

  raw_ptr<dbus::ObjectProxy> rgbkbd_proxy_ = nullptr;

  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<RgbkbdClientImpl> weak_ptr_factory_{this};
};

}  // namespace

void RgbkbdClientImpl::Init(dbus::Bus* bus) {
  CHECK(bus);
  rgbkbd_proxy_ = bus->GetObjectProxy(
      rgbkbd::kRgbkbdServiceName, dbus::ObjectPath(rgbkbd::kRgbkbdServicePath));

  rgbkbd_proxy_->ConnectToSignal(
      rgbkbd::kRgbkbdServiceName, rgbkbd::kCapabilityUpdatedForTesting,
      base::BindRepeating(
          &RgbkbdClientImpl::CapabilityUpdatedForTestingReceived,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&RgbkbdClientImpl::CapabilityUpdatedForTestingConnected,
                     weak_ptr_factory_.GetWeakPtr()));
}

RgbkbdClient::RgbkbdClient() {
  CHECK(!g_instance);
  g_instance = this;
}

RgbkbdClient::~RgbkbdClient() {
  CHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

// static
void RgbkbdClient::Initialize(dbus::Bus* bus) {
  CHECK(bus);
  (new RgbkbdClientImpl())->Init(bus);
}

// static
void RgbkbdClient::InitializeFake() {
  new FakeRgbkbdClient();
}

// static
void RgbkbdClient::Shutdown() {
  CHECK(g_instance);
  delete g_instance;
}

// static
RgbkbdClient* RgbkbdClient::Get() {
  return g_instance;
}

void RgbkbdClient::AddObserver(RgbkbdClient::Observer* observer) {
  observers_.AddObserver(observer);
}

void RgbkbdClient::RemoveObserver(RgbkbdClient::Observer* observer) {
  observers_.RemoveObserver(observer);
}

}  // namespace ash