chromium/chromeos/ash/components/dbus/human_presence/human_presence_dbus_client.cc

// Copyright 2021 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/human_presence/human_presence_dbus_client.h"

#include <memory>
#include <optional>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "chromeos/ash/components/dbus/human_presence/fake_human_presence_dbus_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/hps/dbus-constants.h"

namespace ash {

namespace {

HumanPresenceDBusClient* g_instance = nullptr;

// Extracts result data out of a DBus response.
std::optional<hps::HpsResultProto> UnwrapHpsResult(dbus::Response* response) {
  if (response == nullptr) {
    return std::nullopt;
  }

  dbus::MessageReader reader(response);
  hps::HpsResultProto result;
  if (!reader.PopArrayOfBytesAsProto(&result)) {
    LOG(ERROR) << "Invalid DBus response data";
    return std::nullopt;
  }

  return result;
}

class HumanPresenceDBusClientImpl : public HumanPresenceDBusClient {
 public:
  explicit HumanPresenceDBusClientImpl(dbus::Bus* bus)
      : human_presence_proxy_(
            bus->GetObjectProxy(hps::kHpsServiceName,
                                dbus::ObjectPath(hps::kHpsServicePath))),
        weak_ptr_factory_(this) {
    // Connect to lock-on-leave changed signal.
    human_presence_proxy_->ConnectToSignal(
        hps::kHpsServiceInterface, hps::kHpsSenseChanged,
        base::BindRepeating(
            &HumanPresenceDBusClientImpl::HpsSenseChangedReceived,
            weak_ptr_factory_.GetWeakPtr()),
        base::BindOnce(&HumanPresenceDBusClientImpl::HpsSenseChangedConnected,
                       weak_ptr_factory_.GetWeakPtr()));

    // Connect to snooping protection changed signal.
    human_presence_proxy_->ConnectToSignal(
        hps::kHpsServiceInterface, hps::kHpsNotifyChanged,
        base::BindRepeating(
            &HumanPresenceDBusClientImpl::HpsNotifyChangedReceived,
            weak_ptr_factory_.GetWeakPtr()),
        base::BindOnce(&HumanPresenceDBusClientImpl::HpsNotifyChangedConnected,
                       weak_ptr_factory_.GetWeakPtr()));

    // Monitor daemon restarts.
    human_presence_proxy_->SetNameOwnerChangedCallback(
        base::BindRepeating(&HumanPresenceDBusClientImpl::NameOwnerChanged,
                            weak_ptr_factory_.GetWeakPtr()));
  }

  ~HumanPresenceDBusClientImpl() override = default;

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

  // Called when user presence signal is received.
  void HpsSenseChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    hps::HpsResultProto result;
    if (!reader.PopArrayOfBytesAsProto(&result)) {
      LOG(ERROR) << "Invalid HpsSenseChanged signal: " << signal->ToString();
      return;
    }

    // Notify observers of state change.
    for (auto& observer : observers_) {
      observer.OnHpsSenseChanged(result);
    }
  }

  // Called when snooping signal is received.
  void HpsNotifyChangedReceived(dbus::Signal* signal) {
    dbus::MessageReader reader(signal);
    hps::HpsResultProto result;
    if (!reader.PopArrayOfBytesAsProto(&result)) {
      LOG(ERROR) << "Invalid HpsNotifyChanged signal: " << signal->ToString();
      return;
    }

    // Notify observers of state change.
    for (auto& observer : observers_) {
      observer.OnHpsNotifyChanged(result);
    }
  }

  // Called with a non-empty |new_owner| when the service is restarted, or an
  // empty |new_owner| when the service is shutdown.
  void NameOwnerChanged(const std::string& /* old_owner */,
                        const std::string& new_owner) {
    const auto method =
        new_owner.empty() ? &Observer::OnShutdown : &Observer::OnRestart;
    for (auto& observer : observers_) {
      (observer.*method)();
    }
  }

  // Called when the HpsSenseChanged signal is initially connected.
  void HpsSenseChangedConnected(const std::string& /* interface_name */,
                                const std::string& /* signal_name */,
                                bool success) {
    LOG_IF(ERROR, !success) << "Failed to connect to HpsSenseChanged signal.";
  }

  // Called when the HpsNotifyChanged signal is initially connected.
  void HpsNotifyChangedConnected(const std::string& /* interface_name */,
                                 const std::string& /* signal_name */,
                                 bool success) {
    LOG_IF(ERROR, !success) << "Failed to connect to HpsNotifyChanged signal.";
  }

  // HumanPresenceDBusClient:
  void GetResultHpsSense(GetResultCallback cb) override {
    dbus::MethodCall method_call(hps::kHpsServiceInterface,
                                 hps::kGetResultHpsSense);
    dbus::MessageWriter writer(&method_call);
    human_presence_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&UnwrapHpsResult).Then(std::move(cb)));
  }

  void GetResultHpsNotify(GetResultCallback cb) override {
    dbus::MethodCall method_call(hps::kHpsServiceInterface,
                                 hps::kGetResultHpsNotify);
    dbus::MessageWriter writer(&method_call);
    human_presence_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&UnwrapHpsResult).Then(std::move(cb)));
  }

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

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

  void EnableHpsSense(const hps::FeatureConfig& config) override {
    EnableHumanPresenceFeature(hps::kEnableHpsSense, config);
  }

  void DisableHpsSense() override {
    DisableHumanPresenceFeature(hps::kDisableHpsSense);
  }

  void EnableHpsNotify(const hps::FeatureConfig& config) override {
    EnableHumanPresenceFeature(hps::kEnableHpsNotify, config);
  }

  void DisableHpsNotify() override {
    DisableHumanPresenceFeature(hps::kDisableHpsNotify);
  }

  void WaitForServiceToBeAvailable(
      dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback)
      override {
    human_presence_proxy_->WaitForServiceToBeAvailable(std::move(callback));
  }

 private:
  // Send a method call to the human presence service with given method name and
  // config.
  void EnableHumanPresenceFeature(const std::string& method_name,
                                  const hps::FeatureConfig& config) {
    dbus::MethodCall method_call(hps::kHpsServiceInterface, method_name);
    dbus::MessageWriter writer(&method_call);

    if (!writer.AppendProtoAsArrayOfBytes(config)) {
      LOG(ERROR) << "Failed to encode protobuf for " << method_name;
    } else {
      human_presence_proxy_->CallMethod(&method_call,
                                        dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                        base::DoNothing());
    }
  }

  // Send a method call to HpsDBus with given method name.
  void DisableHumanPresenceFeature(const std::string& method_name) {
    dbus::MethodCall method_call(hps::kHpsServiceInterface, method_name);
    human_presence_proxy_->CallMethod(&method_call,
                                      dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
                                      base::DoNothing());
  }

  const raw_ptr<dbus::ObjectProxy> human_presence_proxy_;

  base::ObserverList<Observer> observers_;

  // Must be last class member.
  base::WeakPtrFactory<HumanPresenceDBusClientImpl> weak_ptr_factory_{this};
};

}  // namespace

HumanPresenceDBusClient::Observer::~Observer() = default;

HumanPresenceDBusClient::HumanPresenceDBusClient() {
  DCHECK_EQ(g_instance, nullptr);
  g_instance = this;
}

HumanPresenceDBusClient::~HumanPresenceDBusClient() {
  DCHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

// static
void HumanPresenceDBusClient::Initialize(dbus::Bus* bus) {
  DCHECK_NE(bus, nullptr);
  new HumanPresenceDBusClientImpl(bus);
}

// static
void HumanPresenceDBusClient::InitializeFake() {
  // Do not create a new fake if it was initialized early in a test, to allow
  // the test to set its own client.
  if (!FakeHumanPresenceDBusClient::Get())
    new FakeHumanPresenceDBusClient();
}

// static
void HumanPresenceDBusClient::Shutdown() {
  DCHECK_NE(g_instance, nullptr);
  delete g_instance;
}

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

}  // namespace ash