chromium/chromeos/ash/services/bluetooth_config/device_operation_handler.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/services/bluetooth_config/device_operation_handler.h"

#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/bluetooth_device.h"

namespace ash::bluetooth_config {

DeviceOperationHandler::PendingOperation::PendingOperation(
    Operation operation_,
    const std::string& device_id_,
    const device::BluetoothTransport& transport_type_,
    OperationCallback callback_)
    : operation(operation_),
      device_id(device_id_),
      transport_type(transport_type_),
      callback(std::move(callback_)) {}

DeviceOperationHandler::PendingOperation::PendingOperation(
    PendingOperation&& other) {
  operation = other.operation;
  device_id = other.device_id;
  transport_type = other.transport_type;
  callback = std::move(other.callback);
}

DeviceOperationHandler::PendingOperation&
DeviceOperationHandler::PendingOperation::operator=(PendingOperation other) {
  operation = other.operation;
  device_id = other.device_id;
  transport_type = other.transport_type;
  callback = std::move(other.callback);
  return *this;
}

DeviceOperationHandler::PendingOperation::~PendingOperation() = default;

// static
const base::TimeDelta DeviceOperationHandler::kOperationTimeout =
    base::Seconds(15);

DeviceOperationHandler::DeviceOperationHandler(
    AdapterStateController* adapter_state_controller)
    : adapter_state_controller_(adapter_state_controller) {
  adapter_state_controller_observation_.Observe(
      adapter_state_controller_.get());
}

DeviceOperationHandler::~DeviceOperationHandler() = default;

void DeviceOperationHandler::Connect(const std::string& device_id,
                                     OperationCallback callback) {
  EnqueueOperation(Operation::kConnect, device_id, std::move(callback));
}

void DeviceOperationHandler::Disconnect(const std::string& device_id,
                                        OperationCallback callback) {
  EnqueueOperation(Operation::kDisconnect, device_id, std::move(callback));
}

void DeviceOperationHandler::Forget(const std::string& device_id,
                                    OperationCallback callback) {
  EnqueueOperation(Operation::kForget, device_id, std::move(callback));
}

void DeviceOperationHandler::HandleFinishedOperation(bool success) {
  current_operation_timer_.Stop();
  if (success) {
    BLUETOOTH_LOG(EVENT) << "Device with id: " << current_operation_->device_id
                         << " succeeded operation: "
                         << current_operation_->operation;
  } else {
    BLUETOOTH_LOG(ERROR) << "Device with id: " << current_operation_->device_id
                         << " failed operation: "
                         << current_operation_->operation;
  }

  // Return the result of the operation to the client.
  std::move(current_operation_->callback).Run(std::move(success));
  current_operation_.reset();
  ProcessQueue();
}

// TODO(gordonseto): Investigate whether we need to manually fail the current
// operation occurring if Bluetooth disables. If we don't, we can remove this
// observer.
void DeviceOperationHandler::OnAdapterStateChanged() {
  if (current_operation_) {
    BLUETOOTH_LOG(DEBUG) << "Device with id: " << current_operation_->device_id
                         << " adapter state changed during operation: "
                         << current_operation_->operation;
  }
  if (IsBluetoothEnabled())
    return;
}

void DeviceOperationHandler::EnqueueOperation(Operation operation,
                                              const std::string& device_id,
                                              OperationCallback callback) {
  BLUETOOTH_LOG(DEBUG) << "Device with id: " << device_id
                       << " enqueueing operation: " << operation << " ("
                       << queue_.size() << " operations already queued)";
  device::BluetoothDevice* device = FindDevice(device_id);
  device::BluetoothTransport type =
      device ? device->GetType()
             : device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID;
  queue_.emplace(operation, device_id, type, std::move(callback));
  ProcessQueue();
}

void DeviceOperationHandler::ProcessQueue() {
  if (current_operation_) {
    BLUETOOTH_LOG(DEBUG) << "Device with id: " << current_operation_->device_id
                         << " continuing operation: "
                         << current_operation_->operation;
    return;
  }

  if (queue_.empty()) {
    BLUETOOTH_LOG(DEBUG) << "No operations queued";
    return;
  }

  PerformNextOperation();
}

void DeviceOperationHandler::PerformNextOperation() {
  current_operation_ = std::move(queue_.front());
  queue_.pop();

  if (!IsBluetoothEnabled()) {
    BLUETOOTH_LOG(ERROR)
        << "Operation failed due to Bluetooth not being enabled, device id: "
        << current_operation_->device_id;
    RecordUserInitiatedReconnectionMetrics(
        device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID,
        base::Time::Now(),
        device::BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
    HandleFinishedOperation(/*success=*/false);
    return;
  }

  BLUETOOTH_LOG(EVENT) << "Device with id: " << current_operation_->device_id
                       << " starting operation: "
                       << current_operation_->operation;
  current_operation_timer_.Start(
      FROM_HERE, kOperationTimeout,
      base::BindOnce(&DeviceOperationHandler::OnOperationTimeout,
                     weak_ptr_factory_.GetWeakPtr()));

  switch (current_operation_->operation) {
    case Operation::kConnect:
      PerformConnect(current_operation_->device_id);
      return;
    case Operation::kDisconnect:
      PerformDisconnect(current_operation_->device_id);
      return;
    case Operation::kForget:
      PerformForget(current_operation_->device_id);
      return;
  }
}

void DeviceOperationHandler::OnOperationTimeout() {
  BLUETOOTH_LOG(ERROR) << "Operation for device with id: "
                       << current_operation_->device_id << " timed out.";
  DCHECK(current_operation_);
  HandleOperationTimeout(current_operation_.value());
  HandleFinishedOperation(/*success=*/false);
}

bool DeviceOperationHandler::IsBluetoothEnabled() const {
  return adapter_state_controller_->GetAdapterState() ==
         mojom::BluetoothSystemState::kEnabled;
}

std::ostream& operator<<(std::ostream& stream,
                         const DeviceOperationHandler::Operation& operation) {
  switch (operation) {
    case DeviceOperationHandler::Operation::kConnect:
      stream << "[Connect]";
      break;
    case DeviceOperationHandler::Operation::kDisconnect:
      stream << "[Disconnect]";
      break;
    case DeviceOperationHandler::Operation::kForget:
      stream << "[Forget]";
      break;
  }
  return stream;
}

}  // namespace ash::bluetooth_config