Implement API to scan multiple services in iOS

PiperOrigin-RevId: 736557350
This commit is contained in:
Guogang Li
2025-03-13 10:34:30 -07:00
committed by Copybara-Service
parent 5eab533260
commit c20cb39f60
4 changed files with 82 additions and 8 deletions
@@ -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<CBUUID *> *)serviceUUIDs
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
completionHandler:
(nullable GNCStartScanningCompletionHandler)completionHandler;
/**
* Stops scanning for peripherals.
*
@@ -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<CBUUID *> *_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<CBUUID *> *)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.
@@ -23,6 +23,7 @@
#include <memory>
#include <string>
#include <vector>
#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<Uuid> &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.
@@ -205,6 +205,49 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLeve
return blockError == nil;
}
bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &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<CBUUID *> *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<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *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];