From 5cb25a1a7042cb9980f8cd767aa77af5f38d8f25 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Thu, 9 Feb 2023 15:00:10 -0800 Subject: [PATCH] Implement Fast Pair discoverable scanner PiperOrigin-RevId: 508484936 --- fastpair/scanning/fastpair/BUILD | 27 ++ .../fastpair/fast_pair_discoverable_scanner.h | 39 +++ .../fast_pair_discoverable_scanner_impl.cc | 213 ++++++++++++ .../fast_pair_discoverable_scanner_impl.h | 90 +++++ ...ast_pair_discoverable_scanner_impl_test.cc | 318 ++++++++++++++++++ 5 files changed, 687 insertions(+) create mode 100644 fastpair/scanning/fastpair/fast_pair_discoverable_scanner.h create mode 100644 fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.cc create mode 100644 fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h create mode 100644 fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc diff --git a/fastpair/scanning/fastpair/BUILD b/fastpair/scanning/fastpair/BUILD index 9c619072..8b8d7243 100644 --- a/fastpair/scanning/fastpair/BUILD +++ b/fastpair/scanning/fastpair/BUILD @@ -17,9 +17,12 @@ licenses(["notice"]) cc_library( name = "scanning", srcs = [ + "fast_pair_discoverable_scanner_impl.cc", "fast_pair_scanner_impl.cc", ], hdrs = [ + "fast_pair_discoverable_scanner.h", + "fast_pair_discoverable_scanner_impl.h", "fast_pair_scanner.h", "fast_pair_scanner_impl.h", ], @@ -31,6 +34,9 @@ cc_library( "//fastpair/common", "//fastpair/dataparser", "//fastpair/internal/ble", + "//fastpair/proto:fastpair_cc_proto", + "//fastpair/repository", + "//fastpair/server_access", "//internal/base", "//internal/platform:base", "//internal/platform:comm", @@ -63,3 +69,24 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "fast_pair_discoverable_scanner_impl_test", + size = "small", + srcs = [ + "fast_pair_discoverable_scanner_impl_test.cc", + ], + shard_count = 16, + deps = [ + ":scanning", + "//fastpair/dataparser", + "//fastpair/server_access:test_support", + "//fastpair/testing", + "//internal/platform:comm", + "//internal/platform:test_util", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner.h b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner.h new file mode 100644 index 00000000..db42ce0d --- /dev/null +++ b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner.h @@ -0,0 +1,39 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_H_ + +#include "absl/functional/any_invocable.h" +#include "fastpair/common/fast_pair_device.h" + +namespace nearby { +namespace fastpair { +using DeviceCallback = absl::AnyInvocable; + +// This class detects Fast Pair 'discoverable' advertisements (see +// https://developers.google.com/nearby/fast-pair/spec#AdvertisingWhenDiscoverable) +// and invokes the |found_callback| when it finds a device within the +// appropriate range. |lost_callback| will be invoked when that device is lost +// to the bluetooth adapter. + +class FastPairDiscoverableScanner { + public: + virtual ~FastPairDiscoverableScanner() = default; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_H_ diff --git a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.cc b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.cc new file mode 100644 index 00000000..97532f72 --- /dev/null +++ b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.cc @@ -0,0 +1,213 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/functional/bind_front.h" +#include "absl/strings/string_view.h" +#include "fastpair/common/constant.h" +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/common/protocol.h" +#include "fastpair/dataparser/fast_pair_data_parser.h" +#include "fastpair/proto/fastpair_rpcs.pb.h" +#include "fastpair/server_access/fast_pair_repository.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace fastpair { + +namespace { +constexpr char kNearbyShareModelId[] = "fc128e"; + +bool IsValidDeviceType(const proto::Device& device) { + return device.device_type() == proto::DeviceType::HEADPHONES || + device.device_type() == proto::DeviceType::SPEAKER || + device.device_type() == proto::DeviceType::TRUE_WIRELESS_HEADPHONES || + device.device_type() == proto::DeviceType::DEVICE_TYPE_UNSPECIFIED; +} + +bool IsSupportedNotificationType(const proto::Device& device) { + // We only allow-list notification types that should trigger a pairing + // notification, since we currently only support pairing. We include + // NOTIFICATION_TYPE_UNSPECIFIED to handle the case where a Provider is + // advertising incorrectly and conservatively allow it to show a notification, + // matching Android behavior. + return device.notification_type() == + proto::NotificationType::NOTIFICATION_TYPE_UNSPECIFIED || + device.notification_type() == proto::NotificationType::FAST_PAIR || + device.notification_type() == proto::NotificationType::FAST_PAIR_ONE; +} +} // namespace + +// FastPairScannerImpl::Factory +FastPairDiscoverableScannerImpl::Factory* + FastPairDiscoverableScannerImpl::Factory::g_test_factory_ = nullptr; + +std::unique_ptr +FastPairDiscoverableScannerImpl::Factory::Create( + std::shared_ptr scanner, + std::shared_ptr adapter, DeviceCallback found_callback, + DeviceCallback lost_callback) { + if (g_test_factory_) { + return g_test_factory_->CreateInstance( + std::move(scanner), std::move(adapter), std::move(found_callback), + std::move(lost_callback)); + } + + return std::make_unique( + std::move(scanner), std::move(adapter), std::move(found_callback), + std::move(lost_callback)); +} + +void FastPairDiscoverableScannerImpl::Factory::SetFactoryForTesting( + Factory* g_test_factory) { + g_test_factory_ = g_test_factory; +} + +FastPairDiscoverableScannerImpl::Factory::~Factory() = default; + +// FastPairScannerImpl +FastPairDiscoverableScannerImpl::FastPairDiscoverableScannerImpl( + std::shared_ptr scanner, + std::shared_ptr adapter, DeviceCallback found_callback, + DeviceCallback lost_callback) + : scanner_(std::move(scanner)), + adapter_(std::move(adapter)), + found_callback_(std::move(found_callback)), + lost_callback_(std::move(lost_callback)) { + scanner_->AddObserver(this); +} + +void FastPairDiscoverableScannerImpl::OnDeviceFound( + const BlePeripheral& peripheral) { + ByteArray fast_pair_service_data = + peripheral.GetAdvertisementBytes(kServiceId); + if (fast_pair_service_data.Empty()) { + NEARBY_LOGS(WARNING) << __func__ + << ": Device doesn't have any Fast Pair Service Data."; + return; + } + + model_id_parse_attempts_[peripheral.GetName()] = 1; + NEARBY_LOGS(WARNING) << __func__ << ": Attempting to get model ID"; + std::vector service_data; + std::string model_id_bytes = fast_pair_service_data.data(); + std::move(std::begin(model_id_bytes), std::end(model_id_bytes), + std::back_inserter(service_data)); + + FastPairDataParser::GetHexModelIdFromServiceData( + service_data, + {[this, peripheral](absl::optional model_id) { + OnModelIdRetrieved(peripheral.GetName(), model_id); + }}); +} + +void FastPairDiscoverableScannerImpl::OnModelIdRetrieved( + const std::string& address, + const absl::optional model_id) { + auto it = model_id_parse_attempts_.find(address); + + // If there's no entry in the map, the device was lost while parsing. + if (it == model_id_parse_attempts_.end()) { + NEARBY_LOGS(WARNING) + << __func__ + << ": Returning early because device as lost while parsing."; + return; + } + + model_id_parse_attempts_.erase(it); + + if (!model_id.has_value()) { + NEARBY_LOGS(INFO) << __func__ + << ": Returning early because no model id was parsed."; + return; + } + + // The Nearby Share feature advertises under the Fast Pair Service Data UUID + // and uses a reserved model ID to enable their 'fast initiation' scenario. + // We must detect this instance and ignore these advertisements since they + // do not correspond to Fast Pair devices that are open to pairing. + if (model_id.value().compare(kNearbyShareModelId) == 0) { + NEARBY_LOGS(WARNING) << __func__ << ": Ignore Nearby Share Model ID."; + return; + } + + FastPairRepository::Get()->GetDeviceMetadata( + model_id.value(), + absl::bind_front( + &FastPairDiscoverableScannerImpl::OnDeviceMetadataRetrieved, this, + address, std::string(model_id.value()))); +} + +void FastPairDiscoverableScannerImpl::OnDeviceMetadataRetrieved( + const std::string& address, const std::string model_id, + DeviceMetadata& device_metadata) { + // Ignore advertisements that aren't for Fast Pair but leverage the service + // UUID. + if (!IsValidDeviceType(device_metadata.GetDetails())) { + NEARBY_LOGS(WARNING) + << __func__ + << ": Invalid device type for Fast Pair. Ignoring this advertisement"; + return; + } + + // Ignore advertisements for unsupported notification types, such as + // APP_LAUNCH which should launch a companion app instead of beginning Fast + // Pair. + if (!IsSupportedNotificationType(device_metadata.GetDetails())) { + NEARBY_LOGS(WARNING) << __func__ + << ": Unsupported notification type for Fast Pair. " + "Ignoring this advertisement"; + return; + } + + FastPairDevice device(model_id, address, Protocol::kFastPairInitialPairing); + NotifyDeviceFound(device); +} + +void FastPairDiscoverableScannerImpl::NotifyDeviceFound( + FastPairDevice& device) { + NEARBY_LOGS(VERBOSE) << "Notify Device found:" + << "BluetoothAddress = " << device.ble_address + << ", Model id = " << device.model_id; + notified_devices_[device.ble_address] = &device; + found_callback_(device); +} + +void FastPairDiscoverableScannerImpl::OnDeviceLost( + const BlePeripheral& peripheral) { + NEARBY_LOGS(INFO) << __func__ << ": Running lost callback"; + + model_id_parse_attempts_.erase(peripheral.GetName()); + + auto it = notified_devices_.find(peripheral.GetName()); + + // Don't invoke callback if we didn't notify this device. + if (it == notified_devices_.end()) return; + FastPairDevice* notified_device = it->second; + notified_devices_.erase(it); + lost_callback_(*notified_device); +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h new file mode 100644 index 00000000..6fec1981 --- /dev/null +++ b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h @@ -0,0 +1,90 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_IMPL_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_IMPL_H_ + +#include +#include +#include +#include + +#include "fastpair/common/fast_pair_device.h" +#include "fastpair/repository/device_metadata.h" +#include "fastpair/scanning/fastpair/fast_pair_discoverable_scanner.h" +#include "fastpair/scanning/fastpair/fast_pair_scanner.h" +#include "internal/base/observer_list.h" +#include "internal/platform/bluetooth_adapter.h" + +namespace nearby { +namespace fastpair { + +class FastPairDiscoverableScannerImpl : public FastPairDiscoverableScanner, + public FastPairScanner::Observer { + public: + class Factory { + public: + static std::unique_ptr Create( + std::shared_ptr scanner, + std::shared_ptr adapter, + DeviceCallback found_callback, DeviceCallback lost_callback); + + static void SetFactoryForTesting(Factory* g_test_factory); + + protected: + virtual ~Factory(); + virtual std::unique_ptr CreateInstance( + std::shared_ptr scanner, + std::shared_ptr adapter, + DeviceCallback found_callback, DeviceCallback lost_callback) = 0; + + private: + static Factory* g_test_factory_; + }; + + FastPairDiscoverableScannerImpl(std::shared_ptr scanner, + std::shared_ptr adapter, + DeviceCallback found_callback, + DeviceCallback lost_callback); + FastPairDiscoverableScannerImpl(const FastPairDiscoverableScannerImpl&) = + delete; + FastPairDiscoverableScannerImpl& operator=( + const FastPairDiscoverableScannerImpl&) = delete; + ~FastPairDiscoverableScannerImpl() override = default; + + // FastPairScanner::Observer + void OnDeviceFound(const BlePeripheral& peripheral) override; + void OnDeviceLost(const BlePeripheral& peripheral) override; + + private: + void OnModelIdRetrieved(const std::string& address, + std::optional model_id); + void OnDeviceMetadataRetrieved(const std::string& address, + const std::string model_id, + DeviceMetadata& device_metadata); + void NotifyDeviceFound(FastPairDevice& device); + + std::shared_ptr scanner_; + std::shared_ptr adapter_; + DeviceCallback found_callback_; + DeviceCallback lost_callback_; + absl::flat_hash_map notified_devices_; + absl::flat_hash_map model_id_parse_attempts_; + ObserverList observer_list_; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_SCANNING_FASTPAIR_FAST_PAIR_DISCOVERABLE_SCANNER_IMPL_H_ diff --git a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc new file mode 100644 index 00000000..c502f534 --- /dev/null +++ b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc @@ -0,0 +1,318 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl.h" + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/synchronization/notification.h" +#include "fastpair/scanning/fastpair/fast_pair_scanner_impl.h" +#include "fastpair/server_access/fake_fast_pair_repository.h" +#include "fastpair/testing/fast_pair_service_data_creator.h" +#include "internal/platform/bluetooth_adapter.h" +#include "internal/platform/medium_environment.h" + +namespace nearby { +namespace fastpair { +namespace { + +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); +constexpr char kValidModelId[] = "718c17"; +constexpr char kInvalidModelId[] = "12345"; +constexpr char kNearbyShareModelId[] = "fc128e"; +constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16"; + +class FakeBlePeripheral : public api::BlePeripheral { + public: + explicit FakeBlePeripheral(absl::string_view name, + absl::string_view model_id) { + name_ = std::string(name); + const std::vector service_data = + FastPairServiceDataCreator::Builder() + .SetModelId(std::string(model_id)) + .Build() + ->CreateServiceData(); + ByteArray advertisement_bytes( + std::string(service_data.begin(), service_data.end())); + advertisement_data_ = advertisement_bytes; + } + + FakeBlePeripheral(const FakeBlePeripheral&) = default; + ~FakeBlePeripheral() override = default; + + std::string GetName() const override { return name_; } + + ByteArray GetAdvertisementBytes( + const std::string& service_id) const override { + return advertisement_data_; + } + + void SetName(const std::string& name) { name_ = name; } + + void SetAdvertisementBytes(ByteArray advertisement_bytes) { + advertisement_data_ = advertisement_bytes; + } + + private: + std::string name_; + ByteArray advertisement_data_; +}; + +class FastPairDiscoverableScannerImplTest : public testing::Test { + public: + void SetUp() override { + SetUpMetadata(); + scanner_ = std::make_shared(); + adapter_ = std::make_shared(); + } + + void SetUpMetadata() { + repository_ = std::make_unique(); + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::TRUE_WIRELESS_HEADPHONES); + repository_->SetFakeMetadata(kValidModelId, metadata); + } + + // void TearDown() override { discoverable_scanner_.reset(); } + + protected: + std::shared_ptr scanner_; + std::unique_ptr repository_; + std::shared_ptr adapter_; + DeviceCallback found_device_callback_; + DeviceCallback lost_device_callback_; +}; + +TEST_F(FastPairDiscoverableScannerImplTest, ValidModelId) { + absl::Notification found_notification; + absl::Notification lost_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + lost_device_callback_ = [&lost_notification](FastPairDevice& device) { + lost_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->OnDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_TRUE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); + scanner_->OnDeviceLost(BlePeripheral(ble_peripheral.get())); + EXPECT_TRUE(lost_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, InvalidModelId) { + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = std::make_unique( + kTestBleDeviceAddress, kInvalidModelId); + scanner_->OnDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, NoServiceData) { + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, ""); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, UnsupportedDeviceType) { + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::AUTOMOTIVE); + repository_->SetFakeMetadata(kValidModelId, metadata); + + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, UnsupportedNotifictionType) { + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::HEADPHONES); + metadata.set_notification_type(proto::NotificationType::APP_LAUNCH); + repository_->SetFakeMetadata(kValidModelId, metadata); + + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, UnspecifiedNotificationType) { + // Set metadata to mimic a device that doesn't specify the notification + // or device type. Since we aren't sure what this device is, we'll show + // the notification to be safe. + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::DEVICE_TYPE_UNSPECIFIED); + metadata.set_notification_type( + proto::NotificationType::NOTIFICATION_TYPE_UNSPECIFIED); + repository_->SetFakeMetadata(kValidModelId, metadata); + + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_TRUE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, V1NotificationType) { + // Set metadata to mimic a V1 device which advertises with no device + // type and a notification type of FAST_PAIR_ONE. + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::DEVICE_TYPE_UNSPECIFIED); + metadata.set_notification_type(proto::NotificationType::FAST_PAIR_ONE); + repository_->SetFakeMetadata(kValidModelId, metadata); + + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_TRUE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, V2NotificationType) { + // Set metadata to mimic a V2 device which advertises with a device + // type of TRUE_WIRELESS_HEADPHONES and a notification type of FAST_PAIR. + proto::Device metadata; + metadata.set_device_type(proto::DeviceType::TRUE_WIRELESS_HEADPHONES); + metadata.set_notification_type(proto::NotificationType::FAST_PAIR); + repository_->SetFakeMetadata(kValidModelId, metadata); + + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->NotifyDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_TRUE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, NearbyShareModelId) { + absl::Notification found_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = std::make_unique( + kTestBleDeviceAddress, kNearbyShareModelId); + scanner_->OnDeviceFound(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(found_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +TEST_F(FastPairDiscoverableScannerImplTest, + DoesntInvokeLostCallbackIfDidntInvokeFound) { + absl::Notification found_notification; + absl::Notification lost_notification; + found_device_callback_ = [&found_notification](FastPairDevice& device) { + found_notification.Notify(); + }; + lost_device_callback_ = [&lost_notification](FastPairDevice& device) { + lost_notification.Notify(); + }; + std::unique_ptr + discoverable_scanner_from_factory = + FastPairDiscoverableScannerImpl::Factory::Create( + scanner_, adapter_, std::move(found_device_callback_), + std::move(lost_device_callback_)); + + auto ble_peripheral = + std::make_unique(kTestBleDeviceAddress, kValidModelId); + scanner_->OnDeviceLost(BlePeripheral(ble_peripheral.get())); + EXPECT_FALSE(lost_notification.WaitForNotificationWithTimeout(kWaitTimeout)); +} + +} // namespace +} // namespace fastpair +} // namespace nearby