0

Reland "[Fast Pair] Add support for retroactive pairing for BLE HID"

This is a reland of commit 0b3859cd0d
and fixes test failures on MSAN builder.

Original change's description:
> [Fast Pair] Add support for retroactive pairing for BLE HID
>
> For BLE devices, since we cannot connect to a message stream to
> retrieve the model ID and the BLE address is already known, the
> only remaining parameter needed is the model ID, which we
> retrieve via GATT characteristic
>
> BUG=b:308092093
> TEST=unit tests
> TEST=manually test initial and retroactive pairing for HID
>
> Change-Id: Ic93cffff190b5927f49e00d23e0781dd1c04efe0
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4995811
> Reviewed-by: Reilly Grant <reillyg@chromium.org>
> Reviewed-by: Jack Shira <jackshira@google.com>
> Commit-Queue: Katherine Lai <laikatherine@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1224991}

BUG=b:308092093
TEST=MSAN browser_tests
TEST=ash_unittests

Change-Id: I941e3ee4fc1c90858f01d71bb1b7da988b59f366
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5038291
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Katherine Lai <laikatherine@chromium.org>
Reviewed-by: Jack Shira <jackshira@google.com>
Cr-Commit-Position: refs/heads/main@{#1225663}
This commit is contained in:
Katherine Lai
2023-11-16 19:42:21 +00:00
committed by Chromium LUCI CQ
parent 3a214002ca
commit 619c703d95
5 changed files with 308 additions and 2 deletions

@ -9,6 +9,7 @@
#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"
@ -219,8 +220,27 @@ void RetroactivePairingDetectorImpl::AttemptRetroactivePairing(
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;
// For BLE devices, since we cannot connect to a message stream to retrieve
// the model ID and the BLE address is already known, the only remaining
// parameter needed is the model ID, which we retrieve via GATT characteristic
if (ash::features::IsFastPairHIDEnabled() &&
device->GetType() == device::BLUETOOTH_TRANSPORT_LE) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": BLE device detected, creating GATT connection";
CreateGattConnection(device);
return;
}
// Attempt to retrieve a MessageStream instance immediately, if it was
// already connected.
MessageStream* message_stream =
@ -232,6 +252,98 @@ void RetroactivePairingDetectorImpl::AttemptRetroactivePairing(
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 {
// If the previous gatt service client did not connect successfully
// or is no longer connected, erase it before attempting to create a new
// gatt connection for the device.
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));
}
void RetroactivePairingDetectorImpl::OnGattClientInitializedCallback(
device::BluetoothDevice* device,
absl::optional<PairFailure> failure) {
if (failure) {
CD_LOG(WARNING, Feature::FP)
<< __func__
<< ": Failed to initialize GATT service client with failure = "
<< failure.value();
return;
}
// If |OnGattClientInitializedCallback| is called without a failure,
// |device*| is expected to exist and be valid.
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,
absl::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) {

@ -158,6 +158,19 @@ class RetroactivePairingDetectorImpl final
// |message_streams_| if a MessageStream exists for the device.
void RemoveExpiredDevicesFromStoredDeviceData();
// Gets or creates a Gatt connection to |device|.
void CreateGattConnection(device::BluetoothDevice* device);
// Internal method called when creating a FastPairGattServiceClient.
void OnGattClientInitializedCallback(device::BluetoothDevice* device,
absl::optional<PairFailure> failure);
// Internal method called to retrieve the model ID of a device.
void OnReadModelId(
const std::string& address,
absl::optional<device::BluetoothGattService::GattErrorCode> error_code,
const std::vector<uint8_t>& value);
// The classic pairing addresses of potential Retroactive Pair supported
// devices that are found in the adapter. We have to store them and wait for a
// MessageStream instance to be created for the device in order to fully

@ -13,6 +13,9 @@
#include "ash/quick_pair/common/logging.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/fast_pair_handshake/fake_fast_pair_gatt_service_client.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/message_stream/fake_bluetooth_socket.h"
#include "ash/quick_pair/message_stream/fake_message_stream_lookup.h"
#include "ash/quick_pair/message_stream/message_stream.h"
@ -56,6 +59,7 @@ const std::vector<uint8_t> kModelIdBytes = {
/*message_code=*/0x01,
/*additional_data_length=*/0x00, 0x03,
/*additional_data=*/0xAA, 0xBB, 0xCC};
const std::vector<uint8_t> kModelIdBytesNoMetadata = {0xAA, 0xBB, 0xCC};
const std::string kModelId = "AABBCC";
const std::vector<uint8_t> kBleAddressBytes = {
@ -97,6 +101,36 @@ CreateTestBluetoothDevice(std::string address) {
/*paired=*/true, /*connected=*/false);
}
class FakeFastPairGattServiceClientImplFactory
: public ash::quick_pair::FastPairGattServiceClientImpl::Factory {
public:
~FakeFastPairGattServiceClientImplFactory() override = default;
ash::quick_pair::FakeFastPairGattServiceClient*
fake_fast_pair_gatt_service_client() {
return fake_fast_pair_gatt_service_client_;
}
private:
// FastPairGattServiceClientImpl::Factory:
std::unique_ptr<ash::quick_pair::FastPairGattServiceClient> CreateInstance(
device::BluetoothDevice* device,
scoped_refptr<device::BluetoothAdapter> adapter,
base::OnceCallback<void(absl::optional<ash::quick_pair::PairFailure>)>
on_initialized_callback) override {
auto fake_fast_pair_gatt_service_client =
std::make_unique<ash::quick_pair::FakeFastPairGattServiceClient>(
device, adapter, std::move(on_initialized_callback));
fake_fast_pair_gatt_service_client_ =
fake_fast_pair_gatt_service_client.get();
return fake_fast_pair_gatt_service_client;
}
raw_ptr<ash::quick_pair::FakeFastPairGattServiceClient,
DanglingUntriaged | ExperimentalAsh>
fake_fast_pair_gatt_service_client_ = nullptr;
};
} // namespace
namespace ash {
@ -111,6 +145,9 @@ class RetroactivePairingDetectorTest
void SetUp() override {
AshTestBase::SetUp();
FastPairGattServiceClientImpl::Factory::SetFactoryForTesting(
&fast_pair_gatt_service_factory_);
adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
@ -168,11 +205,23 @@ class RetroactivePairingDetectorTest
mock_pairer_broker_->NotifyDevicePaired(fp_device);
}
void PairFastPairDeviceWithClassicBluetooth(bool new_paired_status,
std::string classic_address) {
void PairFastPairDeviceWithClassicBluetooth(
bool new_paired_status,
std::string classic_address,
bool test_hid_already_connected = false) {
bluetooth_device_ = CreateTestBluetoothDevice(classic_address);
bluetooth_device_->AddUUID(ash::quick_pair::kFastPairBluetoothUuid);
bluetooth_device_->SetType(
device::BluetoothTransport::BLUETOOTH_TRANSPORT_LE);
auto* bt_device_ptr = bluetooth_device_.get();
if (test_hid_already_connected) {
// Simulate a GATT service client connection already open and connected
auto gatt_service_client = FastPairGattServiceClientImpl::Factory::Create(
bt_device_ptr, adapter_.get(), base::DoNothing());
FastPairGattServiceClientLookup::GetInstance()->InsertFakeForTesting(
bt_device_ptr, std::move(gatt_service_client));
SetGattServiceClientConnected(true);
}
adapter_->AddMockDevice(std::move(bluetooth_device_));
adapter_->NotifyDevicePairedChanged(bt_device_ptr, new_paired_status);
}
@ -200,6 +249,24 @@ class RetroactivePairingDetectorTest
SimulateUserLogin(kUserEmail, user_type);
}
void SetGattServiceClientConnected(bool connected) {
fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
->SetConnected(connected);
}
void RunGattClientInitializedCallback(
absl::optional<PairFailure> pair_failure) {
fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
->RunOnGattClientInitializedCallback(pair_failure);
}
void RunReadModelIdCallback(
absl::optional<device::BluetoothGattService::GattErrorCode> error_code,
const std::vector<uint8_t>& value) {
fast_pair_gatt_service_factory_.fake_fast_pair_gatt_service_client()
->RunReadModelIdCallback(error_code, value);
}
protected:
bool retroactive_pair_found_ = false;
scoped_refptr<Device> retroactive_device_;
@ -216,6 +283,8 @@ class RetroactivePairingDetectorTest
fake_message_stream_lookup_ = nullptr;
std::unique_ptr<FakeFastPairRepository> fast_pair_repository_;
FakeFastPairGattServiceClientImplFactory fast_pair_gatt_service_factory_;
mojo::SharedRemote<mojom::FastPairDataParser> data_parser_remote_;
mojo::PendingRemote<mojom::FastPairDataParser> fast_pair_data_parser_;
std::unique_ptr<FastPairDataParser> data_parser_;
@ -2078,5 +2147,112 @@ TEST_F(RetroactivePairingDetectorTest, NoCrashWhenFootprintsResponseIsSlow) {
fast_pair_repository_->TriggerIsDeviceSavedToAccountCallback();
}
TEST_F(RetroactivePairingDetectorTest, FastPairHID_Success) {
Login(user_manager::UserType::USER_TYPE_REGULAR);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kFastPairSavedDevices,
features::kFastPairSavedDevicesStrictOptIn,
features::kFastPairHID},
/*disabled_features=*/{});
fast_pair_repository_->SetOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
base::RunLoop().RunUntilIdle();
CreateRetroactivePairingDetector();
EXPECT_FALSE(retroactive_pair_found_);
// Test the normal retroactive pair flow of a BLE HID
PairFastPairDeviceWithClassicBluetooth(
/*new_paired_status=*/true, kBleAddress);
SetGattServiceClientConnected(true);
RunGattClientInitializedCallback(/*pair_failure=*/absl::nullopt);
RunReadModelIdCallback(/*error_code=*/absl::nullopt, kModelIdBytesNoMetadata);
EXPECT_TRUE(retroactive_pair_found_);
EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}
TEST_F(RetroactivePairingDetectorTest, FastPairHID_GattConnectionOpen_Success) {
Login(user_manager::UserType::USER_TYPE_REGULAR);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kFastPairSavedDevices,
features::kFastPairSavedDevicesStrictOptIn,
features::kFastPairHID},
/*disabled_features=*/{});
fast_pair_repository_->SetOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
base::RunLoop().RunUntilIdle();
CreateRetroactivePairingDetector();
EXPECT_FALSE(retroactive_pair_found_);
// If GATT connection already open, we expect a read to Model ID
// immediately after.
PairFastPairDeviceWithClassicBluetooth(
/*new_paired_status=*/true, kBleAddress,
/*test_hid_already_connected=*/true);
RunReadModelIdCallback(/*error_code*/ absl::nullopt, kModelIdBytesNoMetadata);
EXPECT_TRUE(retroactive_pair_found_);
EXPECT_EQ(retroactive_device_->ble_address(), kBleAddress);
EXPECT_EQ(retroactive_device_->metadata_id(), kModelId);
}
TEST_F(RetroactivePairingDetectorTest, FastPairHID_GattConnectionFailure) {
Login(user_manager::UserType::USER_TYPE_REGULAR);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kFastPairSavedDevices,
features::kFastPairSavedDevicesStrictOptIn,
features::kFastPairHID},
/*disabled_features=*/{});
fast_pair_repository_->SetOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
base::RunLoop().RunUntilIdle();
CreateRetroactivePairingDetector();
EXPECT_FALSE(retroactive_pair_found_);
PairFastPairDeviceWithClassicBluetooth(
/*new_paired_status=*/true, kBleAddress);
SetGattServiceClientConnected(true);
// If we get an error while create the GATT connection, we shouldn't
// expect a retroactive pairable device to be found.
RunGattClientInitializedCallback(PairFailure::kCreateGattConnection);
EXPECT_FALSE(retroactive_pair_found_);
}
TEST_F(RetroactivePairingDetectorTest, FastPairHID_ReadModelIdFailure) {
Login(user_manager::UserType::USER_TYPE_REGULAR);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kFastPairSavedDevices,
features::kFastPairSavedDevicesStrictOptIn,
features::kFastPairHID},
/*disabled_features=*/{});
fast_pair_repository_->SetOptInStatus(
nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
base::RunLoop().RunUntilIdle();
CreateRetroactivePairingDetector();
EXPECT_FALSE(retroactive_pair_found_);
PairFastPairDeviceWithClassicBluetooth(
/*new_paired_status=*/true, kBleAddress);
SetGattServiceClientConnected(true);
RunGattClientInitializedCallback(/*pair_failure=*/absl::nullopt);
// If we get an error while reading model ID, we shouldn't expect a
// retroactive pairable device to be found.
RunReadModelIdCallback(
/*error_code=*/device::BluetoothGattService::GattErrorCode::kNotSupported,
kModelIdBytesNoMetadata);
EXPECT_FALSE(retroactive_pair_found_);
}
} // namespace quick_pair
} // namespace ash

@ -40,6 +40,7 @@ MockBluetoothDevice::MockBluetoothDevice(MockBluetoothAdapter* adapter,
ON_CALL(*this, GetNameForDisplay())
.WillByDefault(
Return(base::UTF8ToUTF16(name_ ? name_.value() : "Unnamed Device")));
ON_CALL(*this, GetType()).WillByDefault(ReturnPointee(&transport_));
ON_CALL(*this, GetDeviceType())
.WillByDefault(Return(BluetoothDeviceType::UNKNOWN));
ON_CALL(*this, IsPaired()).WillByDefault(ReturnPointee(&paired_));

@ -155,6 +155,8 @@ class MockBluetoothDevice : public BluetoothDevice {
void SetPaired(bool paired) { paired_ = paired; }
void SetType(device::BluetoothTransport transport) { transport_ = transport; }
private:
uint32_t bluetooth_class_;
absl::optional<std::string> name_;
@ -162,6 +164,8 @@ class MockBluetoothDevice : public BluetoothDevice {
BluetoothDevice::UUIDSet uuids_;
bool connected_;
bool paired_;
device::BluetoothTransport transport_ =
device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID;
// Used by tests to save callbacks that will be run in the future.
base::queue<base::OnceClosure> pending_callbacks_;