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

#include "base/time/time.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/chromeos/bluetooth_utils.h"

namespace ash::bluetooth_config {

DeviceOperationHandlerImpl::DeviceOperationHandlerImpl(
    AdapterStateController* adapter_state_controller,
    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
    DeviceNameManager* device_name_manager,
    FastPairDelegate* fast_pair_delegate)
    : DeviceOperationHandler(adapter_state_controller),
      bluetooth_adapter_(std::move(bluetooth_adapter)),
      device_name_manager_(device_name_manager),
      fast_pair_delegate_(fast_pair_delegate) {}

DeviceOperationHandlerImpl::~DeviceOperationHandlerImpl() = default;

void DeviceOperationHandlerImpl::PerformConnect(const std::string& device_id) {
  device::BluetoothDevice* device = FindDevice(device_id);
  last_reconnection_attempt_start_ = base::Time::Now();
  if (!device) {
    BLUETOOTH_LOG(ERROR) << "Connect failed due to device not being "
                            "found, device id: "
                         << device_id;
    RecordUserInitiatedReconnectionMetrics(
        device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID,
        last_reconnection_attempt_start_,
        device::BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
    HandleFinishedOperation(/*success=*/false);
    return;
  }

  // Explicitly create Bluetooth classic connections to avoid wrongfully
  // establishing low-energy connections to a disconnected audio-capable device.
  device->ConnectClassic(
      /*delegate=*/nullptr,
      base::BindOnce(&DeviceOperationHandlerImpl::OnDeviceConnect,
                     weak_ptr_factory_.GetWeakPtr(), device->GetType()));
}

void DeviceOperationHandlerImpl::PerformDisconnect(
    const std::string& device_id) {
  device::BluetoothDevice* device = FindDevice(device_id);
  if (!device) {
    BLUETOOTH_LOG(ERROR) << "Disconnect failed due to device not being "
                            "found, device id: "
                         << device_id;
    HandleFinishedOperation(/*success=*/false);
    return;
  }

  device->Disconnect(
      base::BindOnce(&DeviceOperationHandlerImpl::HandleFinishedOperation,
                     weak_ptr_factory_.GetWeakPtr(), /*success=*/true),
      base::BindOnce(&DeviceOperationHandlerImpl::HandleFinishedOperation,
                     weak_ptr_factory_.GetWeakPtr(), /*success=*/false));
}

void DeviceOperationHandlerImpl::PerformForget(const std::string& device_id) {
  device::BluetoothDevice* device = FindDevice(device_id);
  if (!device) {
    BLUETOOTH_LOG(ERROR) << "Forget failed due to device not being "
                            "found, device id: "
                         << device_id;
    HandleFinishedOperation(/*success=*/false);
    return;
  }

  // Cache the address here in case the device stops existing after forget.
  const std::string address = device->GetAddress();

  // We do not expect "Forget" operations to ever fail, so don't bother passing
  // success and failure callbacks here.
  device->Forget(base::DoNothing(), base::BindOnce(
                                        [](const std::string device_id) {
                                          BLUETOOTH_LOG(ERROR)
                                              << "Forget failed, device id: "
                                              << device_id;
                                        },
                                        device_id));

  device_name_manager_->RemoveDeviceNickname(device_id);
  // This delegate will not exist if the Fast Pair feature flag is disabled.
  if (fast_pair_delegate_) {
    fast_pair_delegate_->ForgetDevice(address);
  }
  HandleFinishedOperation(/*success=*/true);
}

void DeviceOperationHandlerImpl::HandleOperationTimeout(
    const PendingOperation& operation) {
  // Invalidate all BluetoothDevice callbacks for the current operation.
  weak_ptr_factory_.InvalidateWeakPtrs();

  if (operation.operation != Operation::kConnect) {
    return;
  }

  RecordUserInitiatedReconnectionMetrics(
      operation.transport_type, last_reconnection_attempt_start_,
      device::BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
}

device::BluetoothDevice* DeviceOperationHandlerImpl::FindDevice(
    const std::string& device_id) const {
  for (auto* device : bluetooth_adapter_->GetDevices()) {
    if (device->GetIdentifier() != device_id)
      continue;
    return device;
  }
  return nullptr;
}

void DeviceOperationHandlerImpl::RecordUserInitiatedReconnectionMetrics(
    const device::BluetoothTransport transport,
    std::optional<base::Time> reconnection_attempt_start,
    std::optional<device::BluetoothDevice::ConnectErrorCode> error_code) const {
  std::optional<device::ConnectionFailureReason> failure_reason =
      error_code ? std::make_optional(GetConnectionFailureReason(*error_code))
                 : std::nullopt;
  device::RecordUserInitiatedReconnectionAttemptResult(
      failure_reason, device::UserInitiatedReconnectionUISurfaces::kSettings);
  if (reconnection_attempt_start) {
    device::RecordUserInitiatedReconnectionAttemptDuration(
        failure_reason, transport,
        base::Time::Now() - reconnection_attempt_start.value());
  }
}

void DeviceOperationHandlerImpl::OnDeviceConnect(
    device::BluetoothTransport transport,
    std::optional<device::BluetoothDevice::ConnectErrorCode> error_code) {
  if (error_code.has_value()) {
    BLUETOOTH_LOG(ERROR) << "Connect failed with error code: "
                         << error_code.value();
  }

  RecordUserInitiatedReconnectionMetrics(
      transport, last_reconnection_attempt_start_, error_code);

  HandleFinishedOperation(!error_code.has_value());
}

}  // namespace ash::bluetooth_config