[go: nahoru, domu]

[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_;