[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}
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
index 1999ca1..531beb24 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
@@ -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 @@
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 @@
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) {
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
index 9a326d0..69148e1 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
@@ -158,6 +158,19 @@
// |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
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
index a5c2d98c..d74dede 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
@@ -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 @@
/*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 @@
/*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 @@
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 @@
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 @@
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 @@
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 @@
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
diff --git a/device/bluetooth/test/mock_bluetooth_device.cc b/device/bluetooth/test/mock_bluetooth_device.cc
index fa947da..c26d6ff3 100644
--- a/device/bluetooth/test/mock_bluetooth_device.cc
+++ b/device/bluetooth/test/mock_bluetooth_device.cc
@@ -40,6 +40,7 @@
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_));
diff --git a/device/bluetooth/test/mock_bluetooth_device.h b/device/bluetooth/test/mock_bluetooth_device.h
index 3159a70..62f9df3 100644
--- a/device/bluetooth/test/mock_bluetooth_device.h
+++ b/device/bluetooth/test/mock_bluetooth_device.h
@@ -155,6 +155,8 @@
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,7 @@
BluetoothDevice::UUIDSet uuids_;
bool connected_;
bool paired_;
+ device::BluetoothTransport transport_;
// Used by tests to save callbacks that will be run in the future.
base::queue<base::OnceClosure> pending_callbacks_;