#include "ash/quick_pair/pairing/retroactive_pairing_detector_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/session/session_controller.h"
#include "ash/quick_pair/common/constants.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_lookup_impl.h"
#include "ash/quick_pair/message_stream/message_stream.h"
#include "ash/quick_pair/repository/fast_pair_repository.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/cross_device/logging/logging.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/floss/floss_features.h"
namespace {
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;
}
}
constexpr base::TimeDelta kRetroactiveDevicePairingTimeout = base::Seconds(60);
}
namespace ash {
namespace quick_pair {
RetroactivePairingDetectorImpl::RetroactivePairingDetectorImpl(
PairerBroker* pairer_broker,
MessageStreamLookup* message_stream_lookup)
: pairer_broker_(pairer_broker),
message_stream_lookup_(message_stream_lookup) {
if (!ShouldBeEnabledForLoginStatus(
Shell::Get()->session_controller()->login_status())) {
CD_LOG(INFO, Feature::FP)
<< __func__
<< ": No logged in user to enable retroactive pairing scenario";
shell_observation_.Observe(Shell::Get()->session_controller());
return;
}
retroactive_pairing_detector_instatiated_ = true;
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&RetroactivePairingDetectorImpl::OnGetAdapter,
weak_ptr_factory_.GetWeakPtr()));
message_stream_lookup_observation_.Observe(message_stream_lookup_.get());
pairer_broker_observation_.Observe(pairer_broker_.get());
}
void RetroactivePairingDetectorImpl::OnLoginStatusChanged(
LoginStatus login_status) {
if (!ShouldBeEnabledForLoginStatus(login_status) || !pairer_broker_ ||
!message_stream_lookup_ || retroactive_pairing_detector_instatiated_) {
return;
}
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": Logged in user, instantiate retroactive pairing scenario.";
retroactive_pairing_detector_instatiated_ = true;
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&RetroactivePairingDetectorImpl::OnGetAdapter,
weak_ptr_factory_.GetWeakPtr()));
message_stream_lookup_observation_.Observe(message_stream_lookup_.get());
pairer_broker_observation_.Observe(pairer_broker_.get());
}
void RetroactivePairingDetectorImpl::OnGetAdapter(
scoped_refptr<device::BluetoothAdapter> adapter) {
adapter_ = adapter;
adapter_observation_.Reset();
adapter_observation_.Observe(adapter_.get());
}
RetroactivePairingDetectorImpl::~RetroactivePairingDetectorImpl() {
for (auto it = message_streams_.begin(); it != message_streams_.end(); it++) {
it->second->RemoveObserver(this);
}
}
void RetroactivePairingDetectorImpl::AddObserver(
RetroactivePairingDetector::Observer* observer) {
observers_.AddObserver(observer);
}
void RetroactivePairingDetectorImpl::RemoveObserver(
RetroactivePairingDetector::Observer* observer) {
observers_.RemoveObserver(observer);
}
void RetroactivePairingDetectorImpl::OnDevicePaired(
scoped_refptr<Device> device) {
if (!device->classic_address()) {
return;
}
if (base::Contains(potential_retroactive_addresses_,
device->classic_address().value())) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": encountered a false positive for a potential retroactive pairing "
"device. Removing device at address = "
<< device->classic_address().value();
RemoveDeviceInformation(device->classic_address().value());
return;
}
if (ash::features::IsFastPairKeyboardsEnabled() &&
base::Contains(potential_retroactive_addresses_, device->ble_address())) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": encountered a false positive for a potential retroactive pairing "
"device. Removing device at address = "
<< device->ble_address();
RemoveDeviceInformation(device->ble_address());
return;
}
}
void RetroactivePairingDetectorImpl::DevicePairedChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool new_paired_status) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": " << device->GetNameForDisplay()
<< " new_paired_status=" << (new_paired_status ? "paired" : "not paired");
if (!new_paired_status) {
return;
}
const std::string& classic_address = device->GetAddress();
potential_retroactive_addresses_.insert(classic_address);
AddDevicePairingInformation(classic_address);
FastPairRepository::Get()->IsDeviceSavedToAccount(
classic_address,
base::BindOnce(&RetroactivePairingDetectorImpl::AttemptRetroactivePairing,
weak_ptr_factory_.GetWeakPtr(), classic_address));
}
void RetroactivePairingDetectorImpl::AttemptRetroactivePairing(
const std::string& classic_address,
bool is_device_saved_to_account) {
if (!base::Contains(potential_retroactive_addresses_, classic_address)) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": device at " << classic_address
<< ": was removed before call to Footprints completed";
return;
}
if (is_device_saved_to_account) {
CD_LOG(INFO, Feature::FP)
<< __func__ << ": device already saved to user's account";
RemoveDeviceInformation(classic_address);
return;
}
device::BluetoothDevice* device = adapter_->GetDevice(classic_address);
if (!device) {
CD_LOG(WARNING, Feature::FP)
<< __func__ << ": Lost device to potentially retroactively pair to.";
RemoveDeviceInformation(classic_address);
return;
}
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": device = " << classic_address;
if (
floss::features::IsFlossEnabled() &&
device->GetType() == device::BLUETOOTH_TRANSPORT_LE &&
base::Contains(device->GetUUIDs(), kFastPairBluetoothUuid)) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": BLE fast pair device detected, creating GATT connection";
CreateGattConnection(device);
return;
}
MessageStream* message_stream =
message_stream_lookup_->GetMessageStream(classic_address);
if (!message_stream) {
return;
}
message_streams_[classic_address] = message_stream;
GetModelIdAndAddressFromMessageStream(classic_address, message_stream);
}
void RetroactivePairingDetectorImpl::CreateGattConnection(
device::BluetoothDevice* device) {
auto* fast_pair_gatt_service_client =
FastPairGattServiceClientLookup::GetInstance()->Get(device);
if (fast_pair_gatt_service_client) {
if (fast_pair_gatt_service_client->IsConnected()) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": Reusing existing GATT service client to retrieve model ID";
fast_pair_gatt_service_client->ReadModelIdAsync(
base::BindOnce(&RetroactivePairingDetectorImpl::OnReadModelId,
weak_ptr_factory_.GetWeakPtr(), device->GetAddress()));
return;
} else {
FastPairGattServiceClientLookup::GetInstance()->Erase(device);
}
}
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Creating new GATT service client to retrieve model ID";
FastPairGattServiceClientLookup::GetInstance()->Create(
adapter_, device,
base::BindOnce(
&RetroactivePairingDetectorImpl::OnGattClientInitializedCallback,
weak_ptr_factory_.GetWeakPtr(), device->GetAddress()));
}
void RetroactivePairingDetectorImpl::OnGattClientInitializedCallback(
const std::string& address,
std::optional<PairFailure> failure) {
if (failure) {
CD_LOG(WARNING, Feature::FP)
<< __func__
<< ": Failed to initialize GATT service client with failure = "
<< failure.value();
return;
}
device::BluetoothDevice* device = adapter_->GetDevice(address);
if (!device) {
CD_LOG(WARNING, Feature::FP)
<< __func__ << ": Lost device to potentially retroactively pair to.";
return;
}
auto* fast_pair_gatt_service_client =
FastPairGattServiceClientLookup::GetInstance()->Get(device);
if (!fast_pair_gatt_service_client ||
!fast_pair_gatt_service_client->IsConnected()) {
CD_LOG(WARNING, Feature::FP) << __func__
<< ": Fast Pair Gatt Service Client failed to "
"be created or is no longer connected.";
FastPairGattServiceClientLookup::GetInstance()->Erase(device);
return;
}
CD_LOG(VERBOSE, Feature::FP) << __func__
<< ": Fast Pair GATT service client initialized "
"successfully. Reading Model ID.";
fast_pair_gatt_service_client->ReadModelIdAsync(
base::BindOnce(&RetroactivePairingDetectorImpl::OnReadModelId,
weak_ptr_factory_.GetWeakPtr(), device->GetAddress()));
}
void RetroactivePairingDetectorImpl::OnReadModelId(
const std::string& address,
std::optional<device::BluetoothGattService::GattErrorCode> error_code,
const std::vector<uint8_t>& value) {
if (error_code) {
CD_LOG(WARNING, Feature::FP)
<< __func__ << ": Failed to read model ID with failure = "
<< static_cast<uint32_t>(error_code.value());
return;
}
if (value.size() != 3) {
CD_LOG(WARNING, Feature::FP) << __func__ << ": model ID malformed.";
return;
}
std::string model_id;
for (auto byte : value) {
model_id.append(base::StringPrintf("%02X", byte));
}
CD_LOG(INFO, Feature::FP) << __func__ << ": Model ID " << model_id
<< " found for device " << address;
NotifyDeviceFound(model_id, address, address);
}
void RetroactivePairingDetectorImpl::OnMessageStreamConnected(
const std::string& device_address,
MessageStream* message_stream) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ":" << device_address;
if (!message_stream) {
return;
}
if (!base::Contains(potential_retroactive_addresses_, device_address)) {
return;
}
message_streams_[device_address] = message_stream;
GetModelIdAndAddressFromMessageStream(device_address, message_stream);
}
void RetroactivePairingDetectorImpl::AddDevicePairingInformation(
const std::string& device_address) {
CD_LOG(VERBOSE, Feature::FP) << __func__;
device_pairing_information_[device_address].expiry_timestamp =
base::Time::Now() + kRetroactiveDevicePairingTimeout;
RemoveExpiredDevicesFromStoredDeviceData();
}
void RetroactivePairingDetectorImpl::GetModelIdAndAddressFromMessageStream(
const std::string& device_address,
MessageStream* message_stream) {
DCHECK(message_stream);
DCHECK(base::Contains(device_pairing_information_, device_address));
if (!base::Contains(potential_retroactive_addresses_, device_address)) {
return;
}
for (auto& message : message_stream->messages()) {
if (message->is_model_id()) {
device_pairing_information_[device_address].model_id =
message->get_model_id();
} else if (message->is_ble_address_update()) {
device_pairing_information_[device_address].ble_address =
message->get_ble_address_update();
}
}
if (device_pairing_information_[device_address].model_id.empty() ||
device_pairing_information_[device_address].ble_address.empty()) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": BLE address = "
<< (device_pairing_information_[device_address].ble_address.empty()
? "empty"
: device_pairing_information_[device_address].ble_address)
<< " model ID = "
<< (device_pairing_information_[device_address].model_id.empty()
? "empty"
: device_pairing_information_[device_address].model_id)
<< " observing Message Stream for future messages for device = "
<< device_address;
message_stream->AddObserver(this);
return;
}
if (CheckAndRemoveIfDeviceExpired(device_address)) {
return;
}
NotifyDeviceFound(device_pairing_information_[device_address].model_id,
device_pairing_information_[device_address].ble_address,
device_address);
}
bool RetroactivePairingDetectorImpl::CheckAndRemoveIfDeviceExpired(
const std::string& device_address) {
if (base::Time::Now() >=
device_pairing_information_[device_address].expiry_timestamp) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": device at " << device_address
<< " has exceeded the time allotted for detecting "
"retroactive scenario. Removing device information.";
RemoveDeviceInformation(device_address);
return true;
}
return false;
}
void RetroactivePairingDetectorImpl::OnModelIdMessage(
const std::string& device_address,
const std::string& model_id) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": model id = " << model_id
<< "for device = " << device_address;
device_pairing_information_[device_address].model_id = model_id;
CheckPairingInformation(device_address);
}
void RetroactivePairingDetectorImpl::OnBleAddressUpdateMessage(
const std::string& device_address,
const std::string& ble_address) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": ble address " << ble_address
<< " for device = " << device_address;
device_pairing_information_[device_address].ble_address = ble_address;
CheckPairingInformation(device_address);
}
void RetroactivePairingDetectorImpl::CheckPairingInformation(
const std::string& device_address) {
DCHECK(base::Contains(device_pairing_information_, device_address));
if (!base::Contains(potential_retroactive_addresses_, device_address) ||
CheckAndRemoveIfDeviceExpired(device_address)) {
return;
}
if (device_pairing_information_[device_address].model_id.empty() ||
device_pairing_information_[device_address].ble_address.empty()) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": don't have both model id and ble address for device = "
<< device_address;
return;
}
NotifyDeviceFound(device_pairing_information_[device_address].model_id,
device_pairing_information_[device_address].ble_address,
device_address);
}
void RetroactivePairingDetectorImpl::OnDisconnected(
const std::string& device_address) {
CD_LOG(VERBOSE, Feature::FP) << __func__;
message_streams_[device_address]->RemoveObserver(this);
message_streams_.erase(device_address);
}
void RetroactivePairingDetectorImpl::OnMessageStreamDestroyed(
const std::string& device_address) {
CD_LOG(VERBOSE, Feature::FP) << __func__;
message_streams_[device_address]->RemoveObserver(this);
message_streams_.erase(device_address);
}
void RetroactivePairingDetectorImpl::NotifyDeviceFound(
const std::string& model_id,
const std::string& ble_address,
const std::string& classic_address) {
CD_LOG(INFO, Feature::FP) << __func__;
if (features::IsFastPairSavedDevicesEnabled() &&
features::IsFastPairSavedDevicesStrictOptInEnabled()) {
FastPairRepository::Get()->CheckOptInStatus(
base::BindOnce(&RetroactivePairingDetectorImpl::OnCheckOptInStatus,
weak_ptr_factory_.GetWeakPtr(), model_id, ble_address,
classic_address));
return;
}
VerifyDeviceFound(model_id, ble_address, classic_address);
}
void RetroactivePairingDetectorImpl::OnCheckOptInStatus(
const std::string& model_id,
const std::string& ble_address,
const std::string& classic_address,
nearby::fastpair::OptInStatus status) {
CD_LOG(INFO, Feature::FP) << __func__;
if (status != nearby::fastpair::OptInStatus::STATUS_OPTED_IN) {
CD_LOG(INFO, Feature::FP)
<< __func__
<< ": User is not opted in to save devices to their account";
RemoveDeviceInformation(classic_address);
return;
}
VerifyDeviceFound(model_id, ble_address, classic_address);
}
void RetroactivePairingDetectorImpl::VerifyDeviceFound(
const std::string& model_id,
const std::string& ble_address,
const std::string& classic_address) {
CD_LOG(INFO, Feature::FP) << __func__;
device::BluetoothDevice* bluetooth_device =
adapter_->GetDevice(classic_address);
if (!bluetooth_device) {
CD_LOG(WARNING, Feature::FP)
<< __func__ << ": Lost device to potentially retroactively pair to.";
RemoveDeviceInformation(classic_address);
return;
}
auto device = base::MakeRefCounted<Device>(model_id, ble_address,
Protocol::kFastPairRetroactive);
device->set_classic_address(classic_address);
device->set_display_name(bluetooth_device->GetName());
CD_LOG(INFO, Feature::FP)
<< __func__ << ": Found device for Retroactive Pairing " << device;
for (auto& observer : observers_) {
observer.OnRetroactivePairFound(device);
}
DCHECK(device->classic_address());
RemoveDeviceInformation(device->classic_address().value());
}
void RetroactivePairingDetectorImpl::RemoveDeviceInformation(
const std::string& device_address) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": device = " << device_address;
RemoveDeviceInformationHelper(device_address);
RemoveExpiredDevicesFromStoredDeviceData();
}
void RetroactivePairingDetectorImpl::RemoveDeviceInformationHelper(
const std::string& device_address) {
CD_LOG(INFO, Feature::FP) << __func__;
potential_retroactive_addresses_.erase(device_address);
device_pairing_information_.erase(device_address);
if (base::Contains(message_streams_, device_address)) {
message_streams_[device_address]->RemoveObserver(this);
message_streams_.erase(device_address);
}
}
void RetroactivePairingDetectorImpl::
RemoveExpiredDevicesFromStoredDeviceData() {
std::vector<std::string> devices_to_remove;
for (auto it = device_pairing_information_.begin();
it != device_pairing_information_.end(); ++it) {
if (base::Time::Now() >= it->second.expiry_timestamp) {
devices_to_remove.push_back(it->first);
}
}
for (const std::string& device_address : devices_to_remove) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Removing device at " << device_address
<< "that has exceeded the time allotted for detecting "
"retroactive scenario.";
RemoveDeviceInformationHelper(device_address);
}
}
void RetroactivePairingDetectorImpl::OnPairFailure(scoped_refptr<Device> device,
PairFailure failure) {}
void RetroactivePairingDetectorImpl::OnAccountKeyWrite(
scoped_refptr<Device> device,
std::optional<AccountKeyFailure> error) {}
}
}