chromium/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_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 "ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/session/session_controller.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/quick_pair/common/account_key_failure.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_lookup_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake_lookup.h"
#include "ash/quick_pair/repository/fast_pair_repository.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/model/system_tray_model.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "chromeos/ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
#include "components/cross_device/logging/logging.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "third_party/boringssl/src/include/openssl/rand.h"

namespace {

// 15s timeouts chosen to align with Android's Fast Pair implementation.
constexpr base::TimeDelta kCreateBondTimeout = base::Seconds(15);
// Advertisement flag indicating BR/EDR support
constexpr uint8_t kBrEdrNotSupportedFlag = 0x04;
// Key-based Pairing Extended Response Flag indicating if the Provider prefers
// LE bonding
constexpr uint8_t kPrefersLEBonding = 0x40;

std::string MessageTypeToString(
    ash::quick_pair::FastPairMessageType message_type) {
  switch (message_type) {
    case ash::quick_pair::FastPairMessageType::kKeyBasedPairingRequest:
      return "Key-Based Pairing Request";
    case ash::quick_pair::FastPairMessageType::kKeyBasedPairingResponse:
      return "Key-Based Pairing Response";
    case ash::quick_pair::FastPairMessageType::kSeekersPasskey:
      return "Seeker's Passkey";
    case ash::quick_pair::FastPairMessageType::kProvidersPasskey:
      return "Providers' Passkey";
  }
}

bool ShouldBeEnabledForLoginStatus(ash::LoginStatus status) {
  switch (status) {
    case ash::LoginStatus::NOT_LOGGED_IN:
    case ash::LoginStatus::LOCKED:
    case ash::LoginStatus::KIOSK_APP:
    case ash::LoginStatus::GUEST:
    case ash::LoginStatus::PUBLIC:
      return false;
    case ash::LoginStatus::USER:
    case ash::LoginStatus::CHILD:
    default:
      return true;
  }
}

}  // namespace

namespace ash {
namespace quick_pair {

// static
FastPairPairerImpl::Factory* FastPairPairerImpl::Factory::g_test_factory_ =
    nullptr;

// static
std::unique_ptr<FastPairPairer> FastPairPairerImpl::Factory::Create(
    scoped_refptr<device::BluetoothAdapter> adapter,
    scoped_refptr<Device> device,
    base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
    base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
        pair_failed_callback,
    base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>
        account_key_failure_callback,
    base::OnceCallback<void(scoped_refptr<Device>)>
        pairing_procedure_complete) {
  if (g_test_factory_) {
    return g_test_factory_->CreateInstance(
        std::move(adapter), std::move(device), std::move(paired_callback),
        std::move(pair_failed_callback),
        std::move(account_key_failure_callback),
        std::move(pairing_procedure_complete));
  }

  return base::WrapUnique(new FastPairPairerImpl(
      std::move(adapter), std::move(device), std::move(paired_callback),
      std::move(pair_failed_callback), std::move(account_key_failure_callback),
      std::move(pairing_procedure_complete)));
}

// static
void FastPairPairerImpl::Factory::SetFactoryForTesting(
    Factory* g_test_factory) {
  g_test_factory_ = g_test_factory;
}

FastPairPairerImpl::Factory::~Factory() = default;

FastPairPairerImpl::FastPairPairerImpl(
    scoped_refptr<device::BluetoothAdapter> adapter,
    scoped_refptr<Device> device,
    base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
    base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
        pair_failed_callback,
    base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>
        account_key_failure_callback,
    base::OnceCallback<void(scoped_refptr<Device>)> pairing_procedure_complete)
    : adapter_(std::move(adapter)),
      device_(std::move(device)),
      paired_callback_(std::move(paired_callback)),
      pair_failed_callback_(std::move(pair_failed_callback)),
      account_key_failure_callback_(std::move(account_key_failure_callback)),
      pairing_procedure_complete_(std::move(pairing_procedure_complete)) {
  adapter_observation_.Observe(adapter_.get());

  // If this is a v1 pairing, we pass off the responsibility to the Bluetooth
  // pairing dialog, and will listen for the
  // BluetoothAdapter::Observer::DevicePairedChanged event before firing the
  // |paired_callback|. V1 devices only support the "initial pairing" protocol,
  // not the "retroactive" or "subsequent" pairing protocols, so only
  // "initial pairing" metrics are emitted to here.
  if (device_->version().value() == DeviceFastPairVersion::kV1) {
    RecordInitialSuccessFunnelFlow(
        FastPairInitialSuccessFunnelEvent::kV1DeviceDetected);
    RecordFastPairInitializePairingProcessEvent(
        *device_,
        FastPairInitializePairingProcessEvent::kPassedToPairingDialog);
    Shell::Get()->system_tray_model()->client()->ShowBluetoothPairingDialog(
        device_->ble_address());
    return;
  }

  fast_pair_handshake_ = FastPairHandshakeLookup::GetInstance()->Get(device_);

  DCHECK(fast_pair_handshake_);
  DCHECK(fast_pair_handshake_->completed_successfully());

  // If we have a valid handshake, we already have a GATT connection that we
  // maintain in order to prevent addresses changing for some devices when the
  // connection ends.
  StartPairing();
}

FastPairPairerImpl::~FastPairPairerImpl() {
  adapter_->RemovePairingDelegate(this);
}

void FastPairPairerImpl::StartPairing() {
  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPairingStarted,
                            *device_);
  std::string device_address = device_->classic_address().value();
  uint8_t pairing_flags = device_->key_based_pairing_flags().value_or(0);
  if (ash::features::IsFastPairKeyboardsEnabled() &&
      floss::features::IsFlossEnabled() && pairing_flags & kPrefersLEBonding) {
    device_address = device_->ble_address();
  }
  device::BluetoothDevice* bt_device = adapter_->GetDevice(device_address);
  switch (device_->protocol()) {
    case Protocol::kFastPairInitial:
    case Protocol::kFastPairSubsequent:
      // Now that we have validated the decrypted response, we can attempt to
      // retrieve the device from the adapter by the address. If we are able
      // to get the device, and it's not already paired, we can pair directly.
      // Often, we will not be able to find the device this way, and we will
      // have to connect via address and add ourselves as a pairing delegate.
      CD_LOG(VERBOSE, Feature::FP)
          << "Sending pair request to device. Address: " << device_address
          << ". Found device: " << ((bt_device != nullptr) ? "Yes" : "No")
          << ".";

      if (bt_device && bt_device->IsBonded()) {
        CD_LOG(VERBOSE, Feature::FP)
            << __func__
            << ": Trying to pair to device that is already paired; "
               "returning success.";

        RecordProtocolPairingStep(FastPairProtocolPairingSteps::kAlreadyPaired,
                                  *device_);
        RecordProtocolPairingStep(
            FastPairProtocolPairingSteps::kPairingComplete, *device_);
        AttemptRecordingFastPairEngagementFlow(
            *device_,
            FastPairEngagementFlowEvent::kPairingSucceededAlreadyPaired);

        if (!bt_device->IsConnected()) {
          CD_LOG(VERBOSE, Feature::FP)
              << __func__ << ": connecting a paired device";
          create_bond_start_time_ = base::TimeTicks::Now();
          create_bond_timeout_timer_.Start(
              FROM_HERE, kCreateBondTimeout,
              base::BindOnce(&FastPairPairerImpl::OnCreateBondTimeout,
                             weak_ptr_factory_.GetWeakPtr()));
          // On Floss, always connect via classic unless device explicitly
          // doesn't support BREDR
          if (floss::features::IsFlossEnabled() &&
              !(bt_device->GetAdvertisingDataFlags().value_or(0) &
                kBrEdrNotSupportedFlag)) {
            bt_device->ConnectClassic(
                /*pairing_delegate=*/this,
                base::BindOnce(&FastPairPairerImpl::OnConnected,
                               weak_ptr_factory_.GetWeakPtr()));
            return;
          }
          bt_device->Connect(/*pairing_delegate=*/this,
                             base::BindOnce(&FastPairPairerImpl::OnConnected,
                                            weak_ptr_factory_.GetWeakPtr()));
          return;
        }

        std::move(paired_callback_).Run(device_);

        RecordProtocolPairingStep(
            FastPairProtocolPairingSteps::kDeviceConnected, *device_);
        AttemptSendAccountKey();
        return;
      }

      // There are two flows a device can go through for V2 pairing:
      // `device::BluetoothAdapter::ConnectDevice` and
      // `device::BluetoothDevice::Pair`. The flows for each are as follows:
      //
      // ConnectDevice : `ConnectDevice` -> `OnConnectDevice -> `ConfirmPasskey`
      //  -> `WritePasskeyAsync` -> `OnPasskeyResponse` -> `DevicePairedChanged`
      //
      // Pair: `Pair` -> `ConfirmPasskey` -> `WritePasskeyAsync` ->
      // `OnPasskeyResponse` -> `DevicePairedChanged` -> `OnPairConnected` ->
      // `Connect` -> `OnConnected`
      //
      // This timer captures a bonding timeout for the both scenarios.
      create_bond_start_time_ = base::TimeTicks::Now();
      create_bond_timeout_timer_.Start(
          FROM_HERE, kCreateBondTimeout,
          base::BindOnce(&FastPairPairerImpl::OnCreateBondTimeout,
                         weak_ptr_factory_.GetWeakPtr()));

      // TODO(b/266502308): Re-evaluate how we can force a Bluetooth profile
      // for a device to avoid `device::BluetoothAdapter::ConnectDevice` API.
      //
      // The Sony SRS-XB13 is expected to fail `ConnectDevice`, due to SDP
      // collisions, and succeed on a second retry with
      // `device::BluetoothDevice::Pair` because the device profile is ready.
      if (bt_device) {
        pairing_flow_ = FastPairPairingFlow::kPair;
        // On Floss, always connect via classic unless device explicitly
        // doesn't support BREDR. ConnectClassic is equivalent to Pair.
        if (floss::features::IsFlossEnabled() &&
            !(bt_device->GetAdvertisingDataFlags().value_or(0) &
              kBrEdrNotSupportedFlag)) {
          bt_device->ConnectClassic(
              /*pairing_delegate=*/this,
              base::BindOnce(&FastPairPairerImpl::OnPairConnected,
                             weak_ptr_factory_.GetWeakPtr()));
          return;
        }
        bt_device->Pair(/*pairing_delegate=*/this,
                        base::BindOnce(&FastPairPairerImpl::OnPairConnected,
                                       weak_ptr_factory_.GetWeakPtr()));
      } else {
        pairing_flow_ = FastPairPairingFlow::kConnectDevice;
        adapter_->AddPairingDelegate(
            this, device::BluetoothAdapter::PairingDelegatePriority::
                      PAIRING_DELEGATE_PRIORITY_HIGH);
        adapter_->ConnectDevice(
            device_address,
            /*address_type=*/std::nullopt,
            base::BindOnce(&FastPairPairerImpl::OnConnectDevice,
                           weak_ptr_factory_.GetWeakPtr()),
            base::BindOnce(&FastPairPairerImpl::OnConnectError,
                           weak_ptr_factory_.GetWeakPtr()));
      }
      break;
    case Protocol::kFastPairRetroactive:
      // Because the devices are already bonded, BR/EDR bonding and
      // Passkey verification will be skipped and we will directly write an
      // account key to the Provider after a shared secret is established.
      adapter_->RemovePairingDelegate(this);
      AttemptSendAccountKey();
      break;
  }
}

void FastPairPairerImpl::OnConnectDevice(device::BluetoothDevice* device) {
  CD_LOG(VERBOSE, Feature::FP) << __func__;

  if (floss::features::IsFlossEnabled()) {
    // On Floss, ConnectDevice behaves like CreateDevice. It only creates
    // a new device object so we have to follow up with actually Pair()-ing
    // to it.
    CD_LOG(INFO, Feature::FP) << __func__ << " on Floss";

    // Always connect via classic unless device explicitly
    // doesn't support BREDR. ConnectClassic is equivalent to Pair.
    if (!(device->GetAdvertisingDataFlags().value_or(0) &
          kBrEdrNotSupportedFlag)) {
      device->ConnectClassic(
          /*pairing_delegate=*/this,
          base::BindOnce(&FastPairPairerImpl::OnPairConnected,
                         weak_ptr_factory_.GetWeakPtr()));
      return;
    }

    device->Pair(/*pairing_delegate=*/this,
                 base::BindOnce(&FastPairPairerImpl::OnPairConnected,
                                weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kDeviceConnected,
                            *device_);
  RecordConnectDeviceResult(/*success=*/true);
}

void FastPairPairerImpl::OnConnectError(const std::string& error_message) {
  if (!StopCreateBondTimer(__func__)) {
    return;
  }

  CD_LOG(WARNING, Feature::FP) << __func__ << " " << error_message;
  RecordConnectDeviceResult(/*success=*/false);
  std::move(pair_failed_callback_).Run(device_, PairFailure::kAddressConnect);
  // |this| may be destroyed after this line.
}

void FastPairPairerImpl::ConfirmPasskey(device::BluetoothDevice* device,
                                        uint32_t passkey) {
  CD_LOG(VERBOSE, Feature::FP) << __func__;
  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPasskeyNegotiated,
                            *device_);

  auto* ble_device = adapter_->GetDevice(device_->ble_address());

  // TODO(b/251281330): Make handling this edge case more robust.
  //
  // We can get to this point where the BLE instance of the device is lost
  // (due to device specific flaky ADV), thus the FastPairHandshake is null,
  // and |fast_pair_handshake_| is garbage memory, but the classic Bluetooth
  // pairing continues. We stop the pairing in this case and show an error to
  // the user.
  if (!FastPairHandshakeLookup::GetInstance()->Get(device_) || !ble_device) {
    CD_LOG(ERROR, Feature::FP)
        << __func__ << ": BLE device instance lost during passkey exchange";

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    device->CancelPairing();
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kBleDeviceLostMidPair);
    return;
  }

  pairing_device_address_ = device->GetAddress();
  expected_passkey_ = passkey;

  auto* fast_pair_gatt_service_client =
      FastPairGattServiceClientLookup::GetInstance()->Get(ble_device);
  CHECK(fast_pair_gatt_service_client);

  fast_pair_gatt_service_client->WritePasskeyAsync(
      /*message_type=*/0x02, /*passkey=*/expected_passkey_,
      fast_pair_handshake_->fast_pair_data_encryptor(),
      base::BindOnce(&FastPairPairerImpl::OnPasskeyResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairPairerImpl::OnPasskeyResponse(std::vector<uint8_t> response_bytes,
                                           std::optional<PairFailure> failure) {
  CD_LOG(VERBOSE, Feature::FP) << __func__;
  RecordWritePasskeyCharacteristicResult(/*success=*/!failure.has_value());
  RecordProtocolPairingStep(
      FastPairProtocolPairingSteps::kRecievedPasskeyResponse, *device_);

  if (failure) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": Failed to write passkey. Error: " << failure.value();

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    RecordWritePasskeyCharacteristicPairFailure(failure.value());
    std::move(pair_failed_callback_).Run(device_, failure.value());
    // |this| may be destroyed after this line.
    return;
  }

  fast_pair_handshake_->fast_pair_data_encryptor()->ParseDecryptedPasskey(
      response_bytes,
      base::BindOnce(&FastPairPairerImpl::OnParseDecryptedPasskey,
                     weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()));
}

void FastPairPairerImpl::OnParseDecryptedPasskey(
    base::TimeTicks decrypt_start_time,
    const std::optional<DecryptedPasskey>& passkey) {
  if (!passkey) {
    CD_LOG(WARNING, Feature::FP) << "Missing decrypted passkey from parse.";

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    RecordPasskeyCharacteristicDecryptResult(/*success=*/false);
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kPasskeyDecryptFailure);
    // |this| may be destroyed after this line.
    return;
  }

  if (passkey->message_type != FastPairMessageType::kProvidersPasskey) {
    CD_LOG(WARNING, Feature::FP)
        << "Incorrect message type from decrypted passkey. Expected: "
        << MessageTypeToString(FastPairMessageType::kProvidersPasskey)
        << ". Actual: " << MessageTypeToString(passkey->message_type);

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    RecordPasskeyCharacteristicDecryptResult(/*success=*/false);
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kIncorrectPasskeyResponseType);
    // |this| may be destroyed after this line.
    return;
  }

  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPasskeyValidated,
                            *device_);

  if (passkey->passkey != expected_passkey_) {
    CD_LOG(ERROR, Feature::FP)
        << "Passkeys do not match. Expected: " << expected_passkey_
        << ". Actual: " << passkey->passkey;

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    RecordPasskeyCharacteristicDecryptResult(/*success=*/false);
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kPasskeyMismatch);
    // |this| may be destroyed after this line.
    return;
  }

  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPasskeyConfirmed,
                            *device_);
  RecordPasskeyCharacteristicDecryptResult(/*success=*/true);

  device::BluetoothDevice* pairing_device =
      adapter_->GetDevice(pairing_device_address_);

  if (!pairing_device) {
    CD_LOG(WARNING, Feature::FP)
        << "Bluetooth pairing device lost during write to passkey.";

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state.
    StopCreateBondTimer(__func__);
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kPairingDeviceLost);
    // |this| may be destroyed after this line.
    return;
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Passkeys match, confirming pairing";
  pairing_device->ConfirmPairing();
  // DevicePairedChanged() is expected to be called following pairing
  // confirmation.
}

void FastPairPairerImpl::AttemptSendAccountKey() {
  // We only send the account key if we're doing an initial or retroactive
  // pairing. For subsequent pairing, we have to save the account key
  // locally so that we can refer to it in API calls to the server.
  if (device_->protocol() == Protocol::kFastPairSubsequent) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": Saving Account Key locally for subsequent pair";
    FastPairRepository::Get()->WriteAccountAssociationToLocalRegistry(device_);

    // If the Saved Devices feature is enabled and we are utilizing a "loose"
    // interpretation of a user's opt-in status, then we will opt-in the user
    // whenever they pair a Fast Pair device to saving devices to their account.
    // Although we don't surface the user's opt-in status in the Settings'
    // sub-page, this will surface on Android, and show devices saved to the
    // user's account. For subsequent pairing, we opt in the user after they
    // elect to pair with a device already saved to their account.
    if (features::IsFastPairSavedDevicesEnabled() &&
        !features::IsFastPairSavedDevicesStrictOptInEnabled()) {
      CD_LOG(VERBOSE, Feature::FP)
          << __func__ << ": attempting to opt-in the user";
      FastPairRepository::Get()->UpdateOptInStatus(
          nearby::fastpair::OptInStatus::STATUS_OPTED_IN,
          base::BindOnce(&FastPairPairerImpl::OnUpdateOptInStatus,
                         weak_ptr_factory_.GetWeakPtr()));
    }

    std::move(pairing_procedure_complete_).Run(device_);
    return;
  }

  // If there is no signed in user, don't send the account key. This can only
  // happen in an initial pairing scenario since the retroactive pairing
  // scenario is disabled in the RetroactivePairingDetector for users who are
  // not signed in. Because this check happens a long time after the
  // FastPairPairerImpl is instantiated unlike other classes that disable
  // certain paths for users who are not signed in, we do not need to check for
  // a delayed login. At this point, if the user is not logged in, they will not
  // be.
  if (!ShouldBeEnabledForLoginStatus(
          Shell::Get()->session_controller()->login_status())) {
    if (device_->protocol() == Protocol::kFastPairInitial) {
      RecordInitialSuccessFunnelFlow(
          FastPairInitialSuccessFunnelEvent::kGuestModeDetected);
    }

    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": No logged in user to save account key to";
    std::move(pairing_procedure_complete_).Run(device_);
    return;
  }

  // We want to verify the opt in status if the flag is enabled before we write
  // an account key.
  if (features::IsFastPairSavedDevicesEnabled() &&
      features::IsFastPairSavedDevicesStrictOptInEnabled()) {
    FastPairRepository::Get()->CheckOptInStatus(
        base::BindOnce(&FastPairPairerImpl::OnCheckOptInStatus,
                       weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  // It's possible that the user has opted to initial pair to a device that
  // already has an account key saved. We check to see if this is the case
  // before writing a new account key.
  // (b/266953410) This check is performed previously in the Retroactive Pairing
  // Flow, in `RetroactivePairingDetectorImpl::DevicePairedChanged`. To avoid
  // making this redundant request to Footprints, |IsDeviceSavedToAccount| is
  // called only in the Initial Pair scenario.
  if (device_->protocol() != Protocol::kFastPairRetroactive) {
    FastPairRepository::Get()->IsDeviceSavedToAccount(
        device_->classic_address().value(),
        base::BindOnce(&FastPairPairerImpl::OnIsDeviceSavedToAccount,
                       weak_ptr_factory_.GetWeakPtr()));
  } else {
    // If the BLE address has rotated writing the account key is guaranteed to
    // fail. Instead of proceeding, call the callback and return.
    if (ash::features::IsFastPairBleRotationEnabled() &&
        fast_pair_handshake_->DidBleAddressRotate()) {
      // TODO (b/268055837): add metric for when we get in this scenario.
      CD_LOG(VERBOSE, Feature::FP)
          << __func__ << ": BLE Address rotated, running callback";
      fast_pair_handshake_->RunBleAddressRotationCallback();
      return;
    }
    WriteAccountKey();
  }
}

void FastPairPairerImpl::OnCheckOptInStatus(
    nearby::fastpair::OptInStatus status) {
  CD_LOG(VERBOSE, Feature::FP) << __func__;

  if (status != nearby::fastpair::OptInStatus::STATUS_OPTED_IN) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__
        << ": User is not opted in to save devices to their account";
    std::move(pairing_procedure_complete_).Run(device_);
    return;
  }

  // It's possible that the user has opted to initial pair to a device that
  // already has an account key saved. We check to see if this is the case
  // before writing a new account key.
  FastPairRepository::Get()->IsDeviceSavedToAccount(
      device_->classic_address().value(),
      base::BindOnce(&FastPairPairerImpl::OnIsDeviceSavedToAccount,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairPairerImpl::OnIsDeviceSavedToAccount(
    bool is_device_saved_to_account) {
  if (is_device_saved_to_account) {
    // If the device is saved to Footprints, don't write a new account key to
    // the device, and return that we've finished the pairing procedure
    // successfully. We could rework some of our APIs here so that we can call
    // `WriteAccountAssociationToLocalRegistry` similar to how we handle
    // Subsequent pairing above. However, the first time a not discoverable
    // advertisement for this device is found we'll add the account key to our
    // SavedDeviceRegistry as expected.
    CD_LOG(VERBOSE, Feature::FP)
        << __func__
        << ": Device is already saved, skipping write account key. "
           "Pairing procedure complete.";

    if (device_->protocol() == Protocol::kFastPairInitial) {
      RecordInitialSuccessFunnelFlow(
          FastPairInitialSuccessFunnelEvent::kDeviceAlreadyAssociatedToAccount);
    }

    std::move(pairing_procedure_complete_).Run(device_);
    return;
  }

  // If we can't load the user's saved devices for some reason (e.g. offline)
  // |is_device_saved_to_account| will return false even though we didn't
  // properly check Footprints. This will cause us to write a new account key to
  // the device. This may cause problems since the device will have a different
  // account key than what is stored in Footprints, causing the not discoverable
  // advertisement to not be recognized.
  WriteAccountKey();
}

void FastPairPairerImpl::WriteAccountKey() {
  std::array<uint8_t, 16> account_key;
  RAND_bytes(account_key.data(), account_key.size());
  account_key[0] = 0x04;

  if (device_->protocol() == Protocol::kFastPairInitial) {
    RecordInitialSuccessFunnelFlow(
        FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey);
  }

  auto* device = adapter_->GetDevice(device_->ble_address());
  if (!device) {
    CD_LOG(WARNING, Feature::FP)
        << __func__
        << ": device lost when attempting to retrieve GATT service client.";
    std::move(account_key_failure_callback_)
        .Run(device_, AccountKeyFailure::kGattErrorFailed);
    return;
  }

  auto* fast_pair_gatt_service_client =
      FastPairGattServiceClientLookup::GetInstance()->Get(device);
  CHECK(fast_pair_gatt_service_client);

  fast_pair_gatt_service_client->WriteAccountKey(
      account_key, fast_pair_handshake_->fast_pair_data_encryptor(),
      base::BindOnce(&FastPairPairerImpl::OnWriteAccountKey,
                     weak_ptr_factory_.GetWeakPtr(), account_key));
}

void FastPairPairerImpl::OnWriteAccountKey(
    std::array<uint8_t, 16> account_key,
    std::optional<AccountKeyFailure> failure) {
  RecordWriteAccountKeyCharacteristicResult(/*success=*/!failure.has_value());

  if (failure) {
    CD_LOG(WARNING, Feature::FP)
        << "Failed to write account key to device due to error: "
        << failure.value();
    std::move(account_key_failure_callback_).Run(device_, failure.value());
    return;
  }

  if (ash::features::IsFastPairSavedDevicesNicknamesEnabled() &&
      device_->classic_address().has_value() &&
      adapter_->GetDevice(device_->classic_address().value())) {
    device_->set_display_name(
        (adapter_->GetDevice(device_->classic_address().value()))->GetName());
  }

  const std::vector<uint8_t> account_key_vec(account_key.begin(),
                                             account_key.end());

  device_->set_account_key(account_key_vec);
  if (!FastPairRepository::Get()->WriteAccountAssociationToLocalRegistry(
          device_)) {
    CD_LOG(WARNING, Feature::FP)
        << "Failed to write account association to Local Registry.";
  }

  // Devices in the Retroactive Pair scenario are not written to Footprints
  // on account key write, but when the user hits 'Save' on the retroactive pair
  // notification.
  if (device_->protocol() != Protocol::kFastPairRetroactive) {
    FastPairRepository::Get()->WriteAccountAssociationToFootprints(
        device_, account_key_vec);
  }

  // If the Saved Devices feature is enabled and we are utilizing a "loose"
  // interpretation of a user's opt-in status, then we will opt-in the user
  // whenever they pair a Fast Pair device to saving devices to their account.
  // Although we don't surface the user's opt-in status in the Settings'
  // sub-page, this will surface on Android, and show devices saved to the
  // user's account. For initial pairing and retroactive pairing, we opt in the
  // user after after we successfully save an account key to their account.
  if (features::IsFastPairSavedDevicesEnabled() &&
      !features::IsFastPairSavedDevicesStrictOptInEnabled()) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": attempting to opt-in the user";
    FastPairRepository::Get()->UpdateOptInStatus(
        nearby::fastpair::OptInStatus::STATUS_OPTED_IN,
        base::BindOnce(&FastPairPairerImpl::OnUpdateOptInStatus,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__
      << ": Account key written to device. Pairing procedure complete.";

  if (device_->protocol() == Protocol::kFastPairInitial) {
    RecordInitialSuccessFunnelFlow(
        FastPairInitialSuccessFunnelEvent::kAccountKeyWritten);
  }

  std::move(pairing_procedure_complete_).Run(device_);
}

void FastPairPairerImpl::OnUpdateOptInStatus(bool success) {
  RecordSavedDevicesUpdatedOptInStatusResult(/*device=*/*device_,
                                             /*success=*/success);

  if (!success) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": failure";
    return;
  }

  CD_LOG(VERBOSE, Feature::FP) << __func__ << ": success";
}

void FastPairPairerImpl::RequestPinCode(device::BluetoothDevice* device) {
  // Left unimplemented.
}

void FastPairPairerImpl::RequestPasskey(device::BluetoothDevice* device) {
  // Left unimplemented.
}

void FastPairPairerImpl::DisplayPinCode(device::BluetoothDevice* device,
                                        const std::string& pincode) {
  // Left unimplemented.
}

void FastPairPairerImpl::DisplayPasskey(device::BluetoothDevice* device,
                                        uint32_t passkey) {
  // Left unimplemented.
}

void FastPairPairerImpl::KeysEntered(device::BluetoothDevice* device,
                                     uint32_t entered) {
  // Left unimplemented.
}

void FastPairPairerImpl::AuthorizePairing(device::BluetoothDevice* device) {
  // Left unimplemented.
}

void FastPairPairerImpl::DevicePairedChanged(device::BluetoothAdapter* adapter,
                                             device::BluetoothDevice* device,
                                             bool new_paired_status) {
  if (!new_paired_status || !paired_callback_) {
    return;
  }

  if ((device_->classic_address().has_value() &&
       device->GetAddress() == device_->classic_address().value()) ||
      device->GetAddress() == device_->ble_address()) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": Completing pairing procedure " << device_;

    // V1 devices do not set the classic_address() field anywhere else, which is
    // needed to map device addresses to persisted device images. Set the
    // classic address here, which has to happen before paired_callback_ is
    // fired. V2 devices can also set a missing classic address here, although
    // that is not expected to happen.
    if (!device_->classic_address() &&
        device->GetAddressType() ==
            device::BluetoothDevice::AddressType::ADDR_TYPE_PUBLIC) {
      device_->set_classic_address(device->GetAddress());
    }

    bool is_v1_device =
        device_->version().has_value() &&
        device_->version().value() == DeviceFastPairVersion::kV1;
    bool is_pair_branch = pairing_flow_ == FastPairPairingFlow::kPair;

    // DevicePairedChanged is called in the middle of the "kPair" pairing flow,
    // so we don't `AttemptSendAccountKey` until after `OnConnected` fires.
    if (is_pair_branch && !is_v1_device) {
      return;
    }

    // Log and notify that we have successfully paired to the device.
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": Successfully paired to device " << device_;
    RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPairingComplete,
                              *device_);
    std::move(paired_callback_).Run(device_);

    // For V1 devices, this is the end of the pairing fow, since we are using
    // the Bluetooth pairing dialog. V1 devices need to run the
    // |pairing_procedure_complete_| callback in this function since they don't
    // write account keys.
    if (is_v1_device) {
      CD_LOG(VERBOSE, Feature::FP)
          << __func__ << ": pairing procedure completed for V1 device.";
      std::move(pairing_procedure_complete_).Run(device_);
      return;
    }

    // For V2+ devices in the `ConnectDevice` flow, this is the end of the flow.
    // Stop the timer since we have reached a terminal state of success, remove
    // the Pairing Delegate, and write the account key.
    StopCreateBondTimer(__func__);
    CD_LOG(VERBOSE, Feature::FP)
        << __func__
        << ": Stopping create bond timer and attempting to send "
           "account key for ConnectDevice flow";
    adapter_->RemovePairingDelegate(this);
    AttemptSendAccountKey();
  }
}

void FastPairPairerImpl::OnPairConnected(
    std::optional<device::BluetoothDevice::ConnectErrorCode> error) {
  // Check that the timer is still running before continuing. If the timer has
  // expired, then we already have surface an error through
  // `OnCreateBondTimeout` and we should not continue here. This handles the
  // case where this object has not been destroyed yet following a PairFailure,
  // and the `OnPairConnected` callback executes.
  if (!create_bond_timeout_timer_.IsRunning()) {
    return;
  }

  CD_LOG(VERBOSE, Feature::FP) << __func__;

  if (error) {
    CD_LOG(WARNING, Feature::FP)
        << __func__
        << ": Failed to start pairing procedure by pairing to "
           "device due to error: "
        << error.value();

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state for the `Pair` flow.
    StopCreateBondTimer(__func__);
    RecordPairDeviceErrorReason(error.value());
    RecordPairDeviceResult(/*success=*/false);
    // |this| may be destroyed after this line.
    std::move(pair_failed_callback_).Run(device_, PairFailure::kPairingConnect);
    return;
  }

  std::string device_address = device_->classic_address().value();
  uint8_t pairing_flags = device_->key_based_pairing_flags().value_or(0);
  if (ash::features::IsFastPairKeyboardsEnabled() &&
      floss::features::IsFlossEnabled() && pairing_flags & kPrefersLEBonding) {
    device_address = device_->ble_address();
  }
  device::BluetoothDevice* bt_device = adapter_->GetDevice(device_address);
  if (!bt_device) {
    CD_LOG(WARNING, Feature::FP)
        << __func__
        << ": Bluetooth pairing device lost during during device connection";

    // Stop create bond timer on error because at this point, the pairing is
    // in a terminal state for the `Pair` flow.
    StopCreateBondTimer(__func__);
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kPairingDeviceLost);
    // |this| may be destroyed after this line.
    return;
  }

  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPairingComplete,
                            *device_);

  if (floss::features::IsFlossEnabled()) {
    // On Floss, Pair is exactly the same as Connect. Therefore we skip calling
    // Connect().
    CD_LOG(VERBOSE, Feature::FP) << __func__ << ": Skipping Connect on Floss";
    OnConnected(std::nullopt);
    return;
  }

  // We must follow `Pair` with `Connect`. Not all Fast Pair devices initiate
  // a connection following pairing. For device that do initiate connecting
  // following pairing, this may result in `OnConnected` to return a failure,
  // however the connection is successful.
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": attempting connection to device following pair";
  bt_device->Connect(/*pairing_delegate=*/this,
                     base::BindOnce(&FastPairPairerImpl::OnConnected,
                                    weak_ptr_factory_.GetWeakPtr()));
}

void FastPairPairerImpl::OnConnected(
    std::optional<device::BluetoothDevice::ConnectErrorCode> error) {
  // Terminal state for `Pair` flow, so we stop the timer here for this path.
  // We don't need to check which flow we are in here, since we can only
  // reach this point with `Pair`.
  if (!StopCreateBondTimer(__func__)) {
    return;
  }

  CD_LOG(VERBOSE, Feature::FP) << __func__;
  RecordPairDeviceResult(/*success=*/!error.has_value());

  if (error) {
    CD_LOG(WARNING, Feature::FP)
        << __func__
        << ": Failed to start pairing procedure by pairing to "
           "device due to error: "
        << error.value();
    RecordPairDeviceErrorReason(error.value());
    std::move(pair_failed_callback_)
        .Run(device_, PairFailure::kFailedToConnectAfterPairing);
    // |this| may be destroyed after this line.
    return;
  }

  RecordProtocolPairingStep(FastPairProtocolPairingSteps::kDeviceConnected,
                            *device_);

  CD_LOG(INFO, Feature::FP)
      << __func__ << ": starting account key write for `Pair` flow";
  adapter_->RemovePairingDelegate(this);

  std::move(paired_callback_).Run(device_);
  AttemptSendAccountKey();
}

void FastPairPairerImpl::OnCreateBondTimeout() {
  CD_LOG(WARNING, Feature::FP)
      << __func__ << ": Timeout while attempting to create bond with device.";
  std::move(pair_failed_callback_)
      .Run(device_, PairFailure::kCreateBondTimeout);
}

bool FastPairPairerImpl::StopCreateBondTimer(const std::string& callback_name) {
  if (create_bond_timeout_timer_.IsRunning()) {
    RecordCreateBondTime(base::TimeTicks::Now() - create_bond_start_time_);
    create_bond_timeout_timer_.Stop();
    return true;
  }

  CD_LOG(WARNING, Feature::FP)
      << __func__ << ": " << callback_name
      << " called after an attempt to create a bond with device"
         "with classic address "
      << device_->classic_address().value() << " has timed out.";
  return false;
}

}  // namespace quick_pair
}  // namespace ash