chromium/device/bluetooth/bluetooth_device_winrt.cc

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

#include "device/bluetooth/bluetooth_device_winrt.h"

#include <windows.devices.enumeration.h>
#include <windows.foundation.h>

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
#include "base/win/scoped_hstring.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_adapter_winrt.h"
#include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h"
#include "device/bluetooth/bluetooth_pairing_winrt.h"
#include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h"
#include "device/bluetooth/event_utils_winrt.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"

namespace device {

namespace {

using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus;
using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus_Connected;
using ABI::Windows::Devices::Bluetooth::BluetoothError;
using ABI::Windows::Devices::Bluetooth::BluetoothError_Success;
using ABI::Windows::Devices::Bluetooth::BluetoothLEDevice;
using ABI::Windows::Devices::Bluetooth::IBluetoothDeviceId;
using ABI::Windows::Devices::Bluetooth::IBluetoothDeviceIdStatics;
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice;
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice2;
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice4;
using ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattSession;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
    GattSessionStatus;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
    GattSessionStatus_Active;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
    GattSessionStatus_Closed;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattSession;
using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
    IGattSessionStatics;
using ABI::Windows::Devices::Enumeration::IDeviceInformation;
using ABI::Windows::Devices::Enumeration::IDeviceInformation2;
using ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing;
using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing;
using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing2;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Foundation::IClosable;
using Microsoft::WRL::ComPtr;

void PostTask(BluetoothPairingWinrt::ConnectCallback callback,
              std::optional<BluetoothDevice::ConnectErrorCode> error_code) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), error_code));
}

ComPtr<IDeviceInformationPairing> GetDeviceInformationPairing(
    ComPtr<IBluetoothLEDevice> ble_device) {
  if (!ble_device) {
    BLUETOOTH_LOG(DEBUG) << "No BLE device instance present.";
    return nullptr;
  }

  ComPtr<IBluetoothLEDevice2> ble_device_2;
  HRESULT hr = ble_device.As(&ble_device_2);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Obtaining IBluetoothLEDevice2 failed: "
                         << logging::SystemErrorCodeToString(hr);
    return nullptr;
  }

  ComPtr<IDeviceInformation> device_information;
  hr = ble_device_2->get_DeviceInformation(&device_information);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Getting Device Information failed: "
                         << logging::SystemErrorCodeToString(hr);
    return nullptr;
  }

  ComPtr<IDeviceInformation2> device_information_2;
  hr = device_information.As(&device_information_2);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Obtaining IDeviceInformation2 failed: "
                         << logging::SystemErrorCodeToString(hr);
    return nullptr;
  }

  ComPtr<IDeviceInformationPairing> pairing;
  hr = device_information_2->get_Pairing(&pairing);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "DeviceInformation::get_Pairing() failed: "
                         << logging::SystemErrorCodeToString(hr);
    return nullptr;
  }

  return pairing;
}

void CloseDevice(ComPtr<IBluetoothLEDevice> ble_device) {
  if (!ble_device)
    return;

  ComPtr<IClosable> closable;
  HRESULT hr = ble_device.As(&closable);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "As IClosable failed: "
                         << logging::SystemErrorCodeToString(hr);
    return;
  }

  hr = closable->Close();
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "IClosable::Close() failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

void CloseGattSession(ComPtr<IGattSession> gatt_session) {
  if (!gatt_session)
    return;

  ComPtr<IClosable> closable;
  HRESULT hr = gatt_session.As(&closable);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "As IClosable failed: "
                         << logging::SystemErrorCodeToString(hr);
    return;
  }

  hr = closable->Close();
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "IClosable::Close() failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

void RemoveConnectionStatusHandler(IBluetoothLEDevice* ble_device,
                                   EventRegistrationToken token) {
  HRESULT hr = ble_device->remove_ConnectionStatusChanged(token);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Removing ConnectionStatus Handler failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

void RemoveGattSessionStatusHandler(IGattSession* gatt_session,
                                    EventRegistrationToken token) {
  HRESULT hr = gatt_session->remove_SessionStatusChanged(token);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Removing ConnectionStatus Handler failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

void RemoveGattServicesChangedHandler(IBluetoothLEDevice* ble_device,
                                      EventRegistrationToken token) {
  HRESULT hr = ble_device->remove_GattServicesChanged(token);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Removing Gatt Services Changed Handler failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

void RemoveNameChangedHandler(IBluetoothLEDevice* ble_device,
                              EventRegistrationToken token) {
  HRESULT hr = ble_device->remove_NameChanged(token);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Removing NameChanged Handler failed: "
                         << logging::SystemErrorCodeToString(hr);
  }
}

}  // namespace

BluetoothDeviceWinrt::BluetoothDeviceWinrt(BluetoothAdapterWinrt* adapter,
                                           uint64_t raw_address)
    : BluetoothDevice(adapter),
      raw_address_(raw_address),
      address_(CanonicalizeAddress(raw_address)) {
  supports_service_specific_discovery_ = true;
}

BluetoothDeviceWinrt::~BluetoothDeviceWinrt() {
  CloseGattSession(gatt_session_);
  CloseDevice(ble_device_);
  ClearEventRegistrations();
}

uint32_t BluetoothDeviceWinrt::GetBluetoothClass() const {
  // No logging - called too frequenty.
  return 0;
}

std::string BluetoothDeviceWinrt::GetAddress() const {
  return address_;
}

BluetoothDevice::AddressType BluetoothDeviceWinrt::GetAddressType() const {
  NOTIMPLEMENTED();
  return ADDR_TYPE_UNKNOWN;
}

BluetoothDevice::VendorIDSource BluetoothDeviceWinrt::GetVendorIDSource()
    const {
  NOTIMPLEMENTED();
  return VendorIDSource();
}

uint16_t BluetoothDeviceWinrt::GetVendorID() const {
  NOTIMPLEMENTED();
  return 0;
}

uint16_t BluetoothDeviceWinrt::GetProductID() const {
  NOTIMPLEMENTED();
  return 0;
}

uint16_t BluetoothDeviceWinrt::GetDeviceID() const {
  NOTIMPLEMENTED();
  return 0;
}

uint16_t BluetoothDeviceWinrt::GetAppearance() const {
  // No logging - called too frequenty.
  return 0;
}

std::optional<std::string> BluetoothDeviceWinrt::GetName() const {
  if (!ble_device_)
    return local_name_;

  HSTRING name;
  HRESULT hr = ble_device_->get_Name(&name);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Getting Name failed: "
                         << logging::SystemErrorCodeToString(hr);
    return local_name_;
  }

  // Prefer returning |local_name_| over an empty string.
  if (!name)
    return local_name_;

  return base::win::ScopedHString(name).GetAsUTF8();
}

bool BluetoothDeviceWinrt::IsPaired() const {
  ComPtr<IDeviceInformationPairing> pairing =
      GetDeviceInformationPairing(ble_device_);
  if (!pairing) {
    BLUETOOTH_LOG(DEBUG) << "Failed to get DeviceInformationPairing.";
    return false;
  }

  boolean is_paired;
  HRESULT hr = pairing->get_IsPaired(&is_paired);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "DeviceInformationPairing::get_IsPaired() failed: "
                         << logging::SystemErrorCodeToString(hr);
    return false;
  }

  BLUETOOTH_LOG(DEBUG) << "BluetoothDeviceWinrt::IsPaired(): "
                       << (is_paired ? "True" : "False");
  return is_paired;
}

bool BluetoothDeviceWinrt::IsConnected() const {
  return ble_device_ &&
         connection_status_ == BluetoothConnectionStatus_Connected;
}

bool BluetoothDeviceWinrt::IsGattConnected() const {
  if (!observe_gatt_session_status_change_events_)
    return IsConnected();

  return gatt_session_ && gatt_session_status_ == GattSessionStatus_Active;
}

bool BluetoothDeviceWinrt::IsConnectable() const {
  NOTIMPLEMENTED();
  return false;
}

bool BluetoothDeviceWinrt::IsConnecting() const {
  NOTIMPLEMENTED();
  return false;
}

bool BluetoothDeviceWinrt::ExpectingPinCode() const {
  return pairing_ && pairing_->ExpectingPinCode();
}

bool BluetoothDeviceWinrt::ExpectingPasskey() const {
  NOTIMPLEMENTED();
  return false;
}

bool BluetoothDeviceWinrt::ExpectingConfirmation() const {
  NOTIMPLEMENTED();
  return false;
}

void BluetoothDeviceWinrt::GetConnectionInfo(ConnectionInfoCallback callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::SetConnectionLatency(
    ConnectionLatency connection_latency,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::Connect(PairingDelegate* pairing_delegate,
                                   ConnectCallback callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::Pair(PairingDelegate* pairing_delegate,
                                ConnectCallback callback) {
  BLUETOOTH_LOG(DEBUG) << "BluetoothDeviceWinrt::Pair()";
  if (pairing_) {
    BLUETOOTH_LOG(DEBUG) << "Another Pair Operation is already in progress.";
    PostTask(std::move(callback), ERROR_INPROGRESS);
    return;
  }

  ComPtr<IDeviceInformationPairing> pairing =
      GetDeviceInformationPairing(ble_device_);
  if (!pairing) {
    BLUETOOTH_LOG(DEBUG) << "Failed to get DeviceInformationPairing.";
    PostTask(std::move(callback), ERROR_UNKNOWN);
    return;
  }

  ComPtr<IDeviceInformationPairing2> pairing_2;
  HRESULT hr = pairing.As(&pairing_2);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Obtaining IDeviceInformationPairing2 failed: "
                         << logging::SystemErrorCodeToString(hr);
    PostTask(std::move(callback), ERROR_UNKNOWN);
    return;
  }

  ComPtr<IDeviceInformationCustomPairing> custom;
  hr = pairing_2->get_Custom(&custom);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "DeviceInformationPairing::get_Custom() failed: "
                         << logging::SystemErrorCodeToString(hr);
    PostTask(std::move(callback), ERROR_UNKNOWN);
    return;
  }

  // Wrap callback, so that it cleans up the pairing object when run.
  auto wrapped_callback = base::BindOnce(
      [](base::WeakPtr<BluetoothDeviceWinrt> device, ConnectCallback callback,
         std::optional<ConnectErrorCode> error_code) {
        if (device)
          device->pairing_.reset();
        std::move(callback).Run(error_code);
      },
      weak_ptr_factory_.GetWeakPtr(), std::move(callback));

  pairing_ = std::make_unique<BluetoothPairingWinrt>(
      this, pairing_delegate, std::move(custom), std::move(wrapped_callback));
  pairing_->StartPairing();
}

void BluetoothDeviceWinrt::SetPinCode(const std::string& pincode) {
  if (pairing_)
    pairing_->SetPinCode(pincode);
}

void BluetoothDeviceWinrt::SetPasskey(uint32_t passkey) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::ConfirmPairing() {
  if (pairing_)
    pairing_->ConfirmPairing();
}

void BluetoothDeviceWinrt::RejectPairing() {
  if (pairing_)
    pairing_->RejectPairing();
}

void BluetoothDeviceWinrt::CancelPairing() {
  if (pairing_)
    pairing_->CancelPairing();
}

void BluetoothDeviceWinrt::Disconnect(base::OnceClosure callback,
                                      ErrorCallback error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::Forget(base::OnceClosure callback,
                                  ErrorCallback error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::ConnectToService(
    const BluetoothUUID& uuid,
    ConnectToServiceCallback callback,
    ConnectToServiceErrorCallback error_callback) {
  NOTIMPLEMENTED();
}

void BluetoothDeviceWinrt::ConnectToServiceInsecurely(
    const device::BluetoothUUID& uuid,
    ConnectToServiceCallback callback,
    ConnectToServiceErrorCallback error_callback) {
  NOTIMPLEMENTED();
}

// static
std::string BluetoothDeviceWinrt::CanonicalizeAddress(uint64_t address) {
  std::string bluetooth_address =
      CanonicalizeBluetoothAddress(base::StringPrintf("%012llX", address));
  DCHECK(!bluetooth_address.empty());
  return bluetooth_address;
}

void BluetoothDeviceWinrt::UpdateLocalName(
    std::optional<std::string> local_name) {
  if (!local_name)
    return;

  local_name_ = std::move(local_name);
}

void BluetoothDeviceWinrt::CreateGattConnectionImpl(
    std::optional<BluetoothUUID> service_uuid) {
  ComPtr<IBluetoothLEDeviceStatics> device_statics;
  HRESULT hr = GetBluetoothLEDeviceStaticsActivationFactory(&device_statics);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG)
        << "GetBluetoothLEDeviceStaticsActivationFactory failed: "
        << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }

  // Note: Even though we might have obtained a BluetoothLEDevice instance in
  // the past, we need to request a new instance as the old device might have
  // been closed. See also
  // https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client#connecting-to-the-device
  ComPtr<IAsyncOperation<BluetoothLEDevice*>> from_bluetooth_address_op;
  hr = device_statics->FromBluetoothAddressAsync(raw_address_,
                                                 &from_bluetooth_address_op);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG)
        << "BluetoothLEDevice::FromBluetoothAddressAsync failed: "
        << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }

  hr = base::win::PostAsyncResults(
      std::move(from_bluetooth_address_op),
      base::BindOnce(
          &BluetoothDeviceWinrt::OnBluetoothLEDeviceFromBluetoothAddress,
          weak_ptr_factory_.GetWeakPtr()));

  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }

  target_uuid_ = std::move(service_uuid);
  pending_gatt_service_discovery_start_ = true;
}

void BluetoothDeviceWinrt::NotifyGattConnectFailure() {
  // Reset |pending_gatt_service_discovery_start_| so that
  // UpgradeToFullDiscovery() doesn't mistakenly believe GATT discovery is
  // imminent and therefore avoids starting one itself.
  pending_gatt_service_discovery_start_ = false;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidConnectGatt,
                                weak_ptr_factory_.GetWeakPtr(),
                                ConnectErrorCode::ERROR_FAILED));
}

void BluetoothDeviceWinrt::UpgradeToFullDiscovery() {
  // |CreateGattConnectionImpl| has been called previously but having a specific
  // |target_uuid_| was too optimistic and now a complete enumeration of
  // services is needed.
  target_uuid_.reset();

  if (pending_gatt_service_discovery_start_) {
    // There is an imminent call to StartDiscovery(). Resetting |target_uuid_|
    // now will be sufficient to change the discovery that will be started.
    return;
  }

  DCHECK(ble_device_);
  DCHECK(!observe_gatt_session_status_change_events_ || IsGattConnected());

  // Restart discovery.
  StartGattDiscovery();
}

void BluetoothDeviceWinrt::DisconnectGatt() {
  // Closing the device and disposing of all references will trigger a Gatt
  // Disconnection after a short timeout. Since the Gatt Services store a
  // reference to |ble_device_| as well, we need to clear them to drop all
  // remaining references, so that the OS disconnects.
  // Reference:
  // - https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client
  CloseGattSession(gatt_session_);
  CloseDevice(ble_device_);
  ClearGattServices();

  // Stop any pending Gatt Discovery sessions and report an error. This will
  // destroy |gatt_discoverer_| and release remaining references the discoverer
  // might have hold.
  if (gatt_discoverer_)
    OnGattDiscoveryComplete(false);
}

HRESULT BluetoothDeviceWinrt::GetBluetoothLEDeviceStaticsActivationFactory(
    IBluetoothLEDeviceStatics** statics) const {
  return base::win::GetActivationFactory<
      IBluetoothLEDeviceStatics,
      RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice>(statics);
}

HRESULT BluetoothDeviceWinrt::GetGattSessionStaticsActivationFactory(
    IGattSessionStatics** statics) const {
  return base::win::GetActivationFactory<
      IGattSessionStatics,
      RuntimeClass_Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession>(
      statics);
}

void BluetoothDeviceWinrt::OnBluetoothLEDeviceFromBluetoothAddress(
    ComPtr<IBluetoothLEDevice> ble_device) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  if (!ble_device) {
    BLUETOOTH_LOG(DEBUG) << "Getting Device From Bluetooth Address failed.";
    NotifyGattConnectFailure();
    return;
  }

  // As we are about to replace |ble_device_| with |ble_device| existing event
  // handlers need to be unregistered. New ones will be added below.
  ClearEventRegistrations();

  ble_device_ = std::move(ble_device);
  ble_device_->get_ConnectionStatus(&connection_status_);
  connection_changed_token_ = AddTypedEventHandler(
      ble_device_.Get(), &IBluetoothLEDevice::add_ConnectionStatusChanged,
      base::BindRepeating(&BluetoothDeviceWinrt::OnConnectionStatusChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  name_changed_token_ = AddTypedEventHandler(
      ble_device_.Get(), &IBluetoothLEDevice::add_NameChanged,
      base::BindRepeating(&BluetoothDeviceWinrt::OnNameChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  if (!observe_gatt_session_status_change_events_) {
    // GattSession SessionStatusChanged events can not be observed on
    // 1703 (RS2) because BluetoothLEDevice::GetDeviceId() is not
    // available. Instead, initiate GATT discovery which should result
    // in a GATT connection attempt as well and trigger
    // OnConnectionStatusChanged on success.
    if (IsGattConnected()) {
      DidConnectGatt(/*error_code=*/std::nullopt);
    }
    StartGattDiscovery();
    return;
  }

  // Next, obtain a GattSession so we can tell the OS to maintain a GATT
  // connection with |ble_device_|.

  // BluetoothLEDevice::GetDeviceId()
  ComPtr<IBluetoothLEDevice4> ble_device_4;
  HRESULT hr = ble_device_.As(&ble_device_4);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Obtaining IBluetoothLEDevice4 failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }
  ComPtr<IBluetoothDeviceId> bluetooth_device_id;
  hr = ble_device_4->get_BluetoothDeviceId(&bluetooth_device_id);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "BluetoothDeviceId::FromId failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }

  // GattSession::FromDeviceIdAsync()
  IGattSessionStatics* gatt_session_statics = nullptr;
  hr = GetGattSessionStaticsActivationFactory(&gatt_session_statics);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "GetGattSessionStaticsActivationFactory() failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }
  ComPtr<IAsyncOperation<GattSession*>> gatt_session_from_device_id_async_op;
  hr = gatt_session_statics->FromDeviceIdAsync(
      bluetooth_device_id.Get(), &gatt_session_from_device_id_async_op);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "GattSession::FromDeviceId failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }
  hr = base::win::PostAsyncResults(
      std::move(gatt_session_from_device_id_async_op),
      base::BindOnce(&BluetoothDeviceWinrt::OnGattSessionFromDeviceId,
                     weak_ptr_factory_.GetWeakPtr()));
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }
}

void BluetoothDeviceWinrt::OnGattSessionFromDeviceId(
    ComPtr<IGattSession> gatt_session) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(observe_gatt_session_status_change_events_);

  if (!gatt_session) {
    BLUETOOTH_LOG(DEBUG) << "Getting GattSession failed";
    NotifyGattConnectFailure();
    return;
  }

  gatt_session_ = std::move(gatt_session);

  // Tell the OS to automatically establish and maintain a GATT connection.
  HRESULT hr = gatt_session_->put_MaintainConnection(true);
  if (FAILED(hr)) {
    BLUETOOTH_LOG(DEBUG) << "Setting GattSession.MaintainConnection failed: "
                         << logging::SystemErrorCodeToString(hr);
    NotifyGattConnectFailure();
    return;
  }

  // Observe GattSessionStatus changes.
  gatt_session_->get_SessionStatus(&gatt_session_status_);
  gatt_session_status_changed_token_ = AddTypedEventHandler(
      gatt_session_.Get(), &IGattSession::add_SessionStatusChanged,
      base::BindRepeating(&BluetoothDeviceWinrt::OnGattSessionStatusChanged,
                          weak_ptr_factory_.GetWeakPtr()));

  // Check whether we missed the initial GattSessionStatus change notification
  // because the OS had already established a connection.
  if (IsGattConnected()) {
    DidConnectGatt(/*error_code=*/std::nullopt);
    StartGattDiscovery();
  }
}

void BluetoothDeviceWinrt::OnGattSessionStatusChanged(
    IGattSession* gatt_session,
    ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
        IGattSessionStatusChangedEventArgs* event_args) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(observe_gatt_session_status_change_events_);
  DCHECK_EQ(gatt_session_.Get(), gatt_session);

  GattSessionStatus old_status = gatt_session_status_;
  event_args->get_Status(&gatt_session_status_);

  BluetoothError error;
  event_args->get_Error(&error);
  BLUETOOTH_LOG(DEBUG) << "OnGattSessionStatusChanged() status="
                       << gatt_session_status_ << ", error=" << error;

  if (pending_gatt_service_discovery_start_ &&
      error != BluetoothError_Success) {
    NotifyGattConnectFailure();
    return;
  }

  // Spurious status change notifications may occur.
  if (old_status == gatt_session_status_) {
    return;
  }

  if (IsGattConnected()) {
    DidConnectGatt(/*error_code=*/std::nullopt);
    StartGattDiscovery();
  } else {
    gatt_discoverer_.reset();
    ClearGattServices();
    DidDisconnectGatt();
  }
}

void BluetoothDeviceWinrt::OnConnectionStatusChanged(
    IBluetoothLEDevice* ble_device,
    IInspectable* object) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  BluetoothConnectionStatus old_status = connection_status_;
  ble_device->get_ConnectionStatus(&connection_status_);
  BLUETOOTH_LOG(DEBUG) << "OnConnectionStatusChanged() status="
                       << connection_status_;

  // Spurious status change notifications may occur.
  if (old_status == connection_status_) {
    return;
  }

  if (observe_gatt_session_status_change_events_) {
    return;
  }

  if (IsGattConnected()) {
    DidConnectGatt(/*error_code=*/std::nullopt);
  } else {
    gatt_discoverer_.reset();
    ClearGattServices();
    DidDisconnectGatt();
  }
}

void BluetoothDeviceWinrt::OnGattServicesChanged(IBluetoothLEDevice* ble_device,
                                                 IInspectable* object) {
  BLUETOOTH_LOG(DEBUG) << "OnGattServicesChanged()";
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // TODO(crbug.com/40693710): This event fires once for every newly discovered
  // GATT service. Hence, the initial GATT service discovery aborts and restarts
  // itself here once for every service discovered, which is unnecessary and
  // slow.

  // We don't clear out |gatt_services_| here, as we don't want to break
  // existing references to Gatt Services that did not change.
  device_uuids_.ClearServiceUUIDs();

  SetGattServicesDiscoveryComplete(false);
  adapter_->NotifyDeviceChanged(this);
  if (IsGattConnected()) {
    // In order to stop a potential ongoing GATT discovery, the GattDiscoverer
    // is reset and a new discovery is initiated.
    BLUETOOTH_LOG(DEBUG) << "Discovering GATT services anew";
    StartGattDiscovery();
  }
}

void BluetoothDeviceWinrt::OnNameChanged(IBluetoothLEDevice* ble_device,
                                         IInspectable* object) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  adapter_->NotifyDeviceChanged(this);
}

void BluetoothDeviceWinrt::StartGattDiscovery() {
  BLUETOOTH_LOG(DEBUG) << "StartGattDiscovery()";
  pending_gatt_service_discovery_start_ = false;
  if (!gatt_services_changed_token_) {
    gatt_services_changed_token_ = AddTypedEventHandler(
        ble_device_.Get(), &IBluetoothLEDevice::add_GattServicesChanged,
        base::BindRepeating(&BluetoothDeviceWinrt::OnGattServicesChanged,
                            weak_ptr_factory_.GetWeakPtr()));
  }
  gatt_discoverer_ =
      std::make_unique<BluetoothGattDiscovererWinrt>(ble_device_, target_uuid_);
  gatt_discoverer_->StartGattDiscovery(
      base::BindOnce(&BluetoothDeviceWinrt::OnGattDiscoveryComplete,
                     weak_ptr_factory_.GetWeakPtr()));
}

void BluetoothDeviceWinrt::OnGattDiscoveryComplete(bool success) {
  BLUETOOTH_LOG(DEBUG) << "OnGattDiscoveryComplete() success=" << success;
  if (!success) {
    if (!IsGattConnected()) {
      NotifyGattConnectFailure();
    }
    gatt_discoverer_.reset();
    return;
  }

  // Instead of clearing out |gatt_services_| and creating each service from
  // scratch, we create a new map and move already existing services into it in
  // order to preserve pointer stability.
  GattServiceMap gatt_services;
  for (const auto& gatt_service : gatt_discoverer_->GetGattServices()) {
    auto gatt_service_winrt =
        BluetoothRemoteGattServiceWinrt::Create(this, gatt_service);
    if (!gatt_service_winrt)
      continue;

    std::string identifier = gatt_service_winrt->GetIdentifier();
    auto iter = gatt_services_.find(identifier);
    if (iter != gatt_services_.end()) {
      iter = gatt_services.emplace(std::move(*iter)).first;
    } else {
      iter = gatt_services
                 .emplace(std::move(identifier), std::move(gatt_service_winrt))
                 .first;
    }

    static_cast<BluetoothRemoteGattServiceWinrt*>(iter->second.get())
        ->UpdateCharacteristics(gatt_discoverer_.get());
  }

  std::swap(gatt_services, gatt_services_);
  device_uuids_.ReplaceServiceUUIDs(gatt_services_);
  SetGattServicesDiscoveryComplete(true);
  adapter_->NotifyGattServicesDiscovered(this);
  adapter_->NotifyDeviceChanged(this);
  gatt_discoverer_.reset();
}

void BluetoothDeviceWinrt::ClearGattServices() {
  // Clearing |gatt_services_| can trigger callbacks. Move the existing
  // objects into a local variable to avoid re-entrancy into clear().
  GattServiceMap temp_gatt_services;
  temp_gatt_services.swap(gatt_services_);
  temp_gatt_services.clear();

  device_uuids_.ClearServiceUUIDs();
  SetGattServicesDiscoveryComplete(false);
}

void BluetoothDeviceWinrt::ClearEventRegistrations() {
  if (connection_changed_token_) {
    RemoveConnectionStatusHandler(ble_device_.Get(),
                                  *connection_changed_token_);
  }

  if (gatt_session_status_changed_token_) {
    RemoveGattSessionStatusHandler(gatt_session_.Get(),
                                   *gatt_session_status_changed_token_);
  }

  if (gatt_services_changed_token_) {
    RemoveGattServicesChangedHandler(ble_device_.Get(),
                                     *gatt_services_changed_token_);
  }

  if (name_changed_token_)
    RemoveNameChangedHandler(ble_device_.Get(), *name_changed_token_);
}

}  // namespace device