From c20cb39f60d32fda3bbe5bfc468f6ac8da4d6397 Mon Sep 17 00:00:00 2001 From: Guogang Li Date: Thu, 13 Mar 2025 10:33:05 -0700 Subject: [PATCH] Implement API to scan multiple services in iOS PiperOrigin-RevId: 736557350 --- .../apple/Mediums/BLEv2/GNCBLEMedium.h | 15 +++++++ .../apple/Mediums/BLEv2/GNCBLEMedium.m | 26 +++++++---- .../implementation/apple/ble_medium.h | 6 +++ .../implementation/apple/ble_medium.mm | 43 +++++++++++++++++++ 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h index 51574091..9f81b4e3 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h @@ -135,6 +135,21 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler; +/** + * Scans for peripherals that are advertising by one of the specified services. + * + * @param serviceUUIDs The service UUIDs to scan for. If multiple services are specified, the + * advertisementFoundHandler will be called for each service that is + * discovered. + * @param advertisementFoundHandler Called on a private queue when a peripheral has been discovered. + * @param completionHandler Called on a private queue with @c nil if successfully started scanning + * or an error if one has occured. + */ +- (void)startScanningForMultipleServices:(NSArray *)serviceUUIDs + advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler + completionHandler: + (nullable GNCStartScanningCompletionHandler)completionHandler; + /** * Stops scanning for peripherals. * diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m index 84bce0b7..0f4b91df 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m @@ -43,8 +43,8 @@ static NSError *AlreadyScanningError() { // The active GATT server, or @nil if one hasn't been started yet. GNCBLEGATTServer *_server; - // The service that is being actively scanned for, or @c nil if not currently scanning. - CBUUID *_serviceUUID; + // The services that is being actively scanned for. + NSMutableArray *_scanningServiceUUIDs; // The handler called when an advertisement for the service represented by @c _serviceUUID has // been discovered. This will be called continuously, until the peripheral disappears. @@ -80,6 +80,7 @@ static NSError *AlreadyScanningError() { _centralManager.centralDelegate = self; _connectionCompletionHandlers = [NSMutableDictionary dictionary]; _disconnectionHandlers = [NSMutableDictionary dictionary]; + _scanningServiceUUIDs = [NSMutableArray array]; } return self; } @@ -118,15 +119,24 @@ static NSError *AlreadyScanningError() { - (void)startScanningForService:(CBUUID *)serviceUUID advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler { + [self startScanningForMultipleServices:@[ serviceUUID ] + advertisementFoundHandler:advertisementFoundHandler + completionHandler:completionHandler]; +} + +- (void)startScanningForMultipleServices:(NSArray *)serviceUUIDs + advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler + completionHandler: + (nullable GNCStartScanningCompletionHandler)completionHandler { dispatch_async(_queue, ^{ - if (_serviceUUID) { + if (_scanningServiceUUIDs.count > 0) { if (completionHandler) { completionHandler(AlreadyScanningError()); } return; } - _serviceUUID = serviceUUID; + [_scanningServiceUUIDs addObjectsFromArray:serviceUUIDs]; _advertisementFoundHandler = advertisementFoundHandler; [self internalStartScanningIfPoweredOn]; @@ -139,7 +149,7 @@ static NSError *AlreadyScanningError() { - (void)stopScanningWithCompletionHandler: (nullable GNCStopScanningCompletionHandler)completionHandler { dispatch_async(_queue, ^{ - _serviceUUID = nil; + [_scanningServiceUUIDs removeAllObjects]; _advertisementFoundHandler = nil; [_centralManager stopScan]; if (completionHandler) { @@ -179,11 +189,11 @@ static NSError *AlreadyScanningError() { // then back on. This will be called anytime the central manager's state changes, so // @c scanForPeripheralsWithServices:options: will be called anytime state transitions back to // powered on. - if (_centralManager.state == CBManagerStatePoweredOn && _serviceUUID != nil) { + if (_centralManager.state == CBManagerStatePoweredOn && _scanningServiceUUIDs.count > 0) { // Stop scanning just in case something outside of this class is already scanning. [_centralManager stopScan]; [_centralManager - scanForPeripheralsWithServices:@[ _serviceUUID ] + scanForPeripheralsWithServices:_scanningServiceUUIDs // Nearby relies on the existence of an advertisement for endpoint // discovery/lost events, so we must set this key to keep the stream // of duplicate delegate events flowing. This has adverse effect on @@ -214,7 +224,7 @@ static NSError *AlreadyScanningError() { NSData *data = [[NSData alloc] initWithBase85EncodedString:localName]; #else NSData *data = [[NSData alloc] initWithWebSafeBase64EncodedString:localName]; -#endif // defined(NC_IOS_SDK) +#endif // defined(NC_IOS_SDK) // A Nearby Apple advertisement should only have a single service, so simply grab the first one if // it exists. diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index 1c544740..fbfffa8b 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -23,6 +23,7 @@ #include #include +#include #include "internal/platform/cancellation_flag.h" #include "internal/platform/implementation/ble_v2.h" @@ -86,6 +87,11 @@ class BleMedium : public api::ble_v2::BleMedium { bool StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level, api::ble_v2::BleMedium::ScanCallback callback) override; + // Starts scanning on multiple services and returns whether or not it was successful. + bool StartMultipleServicesScanning(const std::vector &service_uuids, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BleMedium::ScanCallback callback) override; + // Stops scanning. // // Returns whether or not scanning was successfully stopped. diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index 8f8be746..c54601b1 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -205,6 +205,49 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLeve return blockError == nil; } +bool BleMedium::StartMultipleServicesScanning(const std::vector &service_uuids, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BleMedium::ScanCallback callback) { + absl::MutexLock lock(&peripherals_mutex_); + + if (service_uuids.empty()) { + GTMLoggerError(@"No service UUIDs provided"); + return false; + } + + NSMutableArray *serviceUUIDs = [NSMutableArray arrayWithCapacity:service_uuids.size()]; + for (const Uuid &service_uuid : service_uuids) { + [serviceUUIDs addObject:CBUUID128FromCPP(service_uuid)]; + } + + scan_cb_ = std::move(callback); + + // Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the + // map every time we stopped a scan, we would not be able to connect to peripherals that we + // discovered in that scan session. + peripherals_.clear(); + + socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]]; + [socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]]; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block NSError *blockError = nil; + [medium_ startScanningForMultipleServices:serviceUUIDs + advertisementFoundHandler:^(id peripheral, + NSDictionary *serviceData) { + HandleAdvertisementFound(peripheral, serviceData); + } + completionHandler:^(NSError *error) { + if (error != nil) { + GTMLoggerError(@"Failed to start scanning for multiple services: %@", error); + blockError = error; + } + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return blockError == nil; +} + bool BleMedium::StopScanning() { [socketCentralManager_ stopNoScanMode];