diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h index 2461d5f6..1b94731a 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h @@ -19,11 +19,39 @@ NS_ASSUME_NONNULL_BEGIN +/** + * A block to be invoked when a call to + * @c createCharacteristicWithServiceID:characteristicUUID:permissions:properties:completionHandler: + * has completed. + * + * @param characteristic The created characteristic, or @c nil if an error occurred. + * @param error The cause of the failure, or @c nil if no error occurred. + */ typedef void (^GNCCreateCharacteristicCompletionHandler)( GNCBLEGATTCharacteristic *_Nullable characteristic, NSError *_Nullable error); + +/** + * A block to be invoked when a call to @c updateCharacteristic:value:completionHandler: has + * completed. + * + * @param error The cause of the failure, or @c nil if no error occurred. + */ typedef void (^GNCUpdateCharacteristicCompletionHandler)(NSError *_Nullable error); + +/** + * A block to be invoked when a call to @c startAdvertisingData:completionHandler: has completed. + * + * @param error The cause of the failure, or @c nil if no error occurred. + */ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error); +/** + * A block to be invoked when a call to @c stopAdvertisingWithcompletionHandler: has completed. + * + * @param error The cause of the failure, or @c nil if no error occurred. + */ +typedef void (^GNCStopAdvertisingCompletionHandler)(NSError *_Nullable error); + /** * An object that manages and advertises GATT characteritics. * @@ -82,6 +110,15 @@ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error); - (void)startAdvertisingData:(NSDictionary *)serviceData completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler; +/** + * Stops advertising all service data. + * + * @param completionHandler Called on a private queue with @c nil if successfully stopped + * advertising or an error if one has occured. + */ +- (void)stopAdvertisingWithCompletionHandler: + (nullable GNCStopAdvertisingCompletionHandler)completionHandler; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.m index 1f996d69..703f6edd 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.m @@ -223,6 +223,17 @@ static char *const kGNCBLEGATTServerQueueLabel = "com.nearby.GNCBLEGATTServer"; }); } +- (void)stopAdvertisingWithCompletionHandler: + (nullable GNCStopAdvertisingCompletionHandler)completionHandler { + dispatch_async(_queue, ^{ + _advertisementData = nil; + [_peripheralManager stopAdvertising]; + if (completionHandler) { + completionHandler(nil); + } + }); +} + #pragma mark - Internal - (void)internalAddPendingServicesIfPoweredOn { diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h index d81b4975..51574091 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h @@ -30,6 +30,13 @@ NS_ASSUME_NONNULL_BEGIN */ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error); +/** + * A block to be invoked when a call to @c stopAdvertisingWithcompletionHandler: has completed. + * + * @param error The cause of the failure, or @c nil if no error occurred. + */ +typedef void (^GNCStopAdvertisingCompletionHandler)(NSError *_Nullable error); + /** * A block to be invoked when a peripheral’s advertisement has been discovered. * @@ -50,6 +57,13 @@ typedef void (^GNCAdvertisementFoundHandler)(id peripheral, */ typedef void (^GNCStartScanningCompletionHandler)(NSError *_Nullable error); +/** + * A block to be invoked when a call to @c stopScanningWithCompletionHandler: has completed. + * + * @param error The cause of the failure, or @c nil if no error occurred. + */ +typedef void (^GNCStopScanningCompletionHandler)(NSError *_Nullable error); + /** * A block to be invoked when a call to @c startGATTServerWithCompletionHandler: has completed. * @@ -100,6 +114,15 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c - (void)startAdvertisingData:(NSDictionary *)serviceData completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler; +/** + * Stops advertising all service data. + * + * @param completionHandler Called on a private queue with @c nil if successfully stopped + * advertising or an error if one has occured. + */ +- (void)stopAdvertisingWithCompletionHandler: + (nullable GNCStopAdvertisingCompletionHandler)completionHandler; + /** * Scans for peripherals that are advertising the specified service. * @@ -112,6 +135,15 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler; +/** + * Stops scanning for peripherals. + * + * @param completionHandler Called on a private queue with @c nil if successfully stopped + * scanning or an error if one has occured. + */ +- (void)stopScanningWithCompletionHandler: + (nullable GNCStopScanningCompletionHandler)completionHandler; + /** * Starts a GATT server. * diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m index 7f1c0735..139b5169 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m @@ -101,6 +101,19 @@ static NSError *AlreadyScanningError() { }); } +- (void)stopAdvertisingWithCompletionHandler: + (nullable GNCStopAdvertisingCompletionHandler)completionHandler { + dispatch_async(_queue, ^{ + if (!_server) { + if (completionHandler) { + completionHandler(nil); + } + return; + } + [_server stopAdvertisingWithCompletionHandler:completionHandler]; + }); +} + - (void)startScanningForService:(CBUUID *)serviceUUID advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler { @@ -122,6 +135,18 @@ static NSError *AlreadyScanningError() { }); } +- (void)stopScanningWithCompletionHandler: + (nullable GNCStopScanningCompletionHandler)completionHandler { + dispatch_async(_queue, ^{ + _serviceUUID = nil; + _advertisementFoundHandler = nil; + [_centralManager stopScan]; + if (completionHandler) { + completionHandler(nil); + } + }); +} + - (void)startGATTServerWithCompletionHandler: (nullable GNCGATTServerCompletionHandler)completionHandler { dispatch_async(_queue, ^{ diff --git a/internal/platform/implementation/apple/Tests/GNCBLEGATTServerTest.m b/internal/platform/implementation/apple/Tests/GNCBLEGATTServerTest.m index a016a47f..ada28625 100644 --- a/internal/platform/implementation/apple/Tests/GNCBLEGATTServerTest.m +++ b/internal/platform/implementation/apple/Tests/GNCBLEGATTServerTest.m @@ -614,4 +614,44 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000 [self waitForExpectations:@[ expectation ] timeout:3]; } +- (void)testStartStopStartAdvertising { + GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init]; + + GNCBLEGATTServer *gattServer = + [[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager]; + + [fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn]; + + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start advertising."]; + + [gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]} + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF3"]); + [gattServer stopAdvertisingWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertFalse(fakePeripheralManager.isAdvertising); + [gattServer + startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]} + completionHandler:^(NSError *error) { + XCTAssertNil(error); + XCTAssertTrue(fakePeripheralManager.isAdvertising); + NSDictionary *data = + fakePeripheralManager.advertisementData; + XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @""); + XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0], + [CBUUID UUIDWithString:@"FEF4"]); + [expectation fulfill]; + }]; + }]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:3]; +} + @end diff --git a/internal/platform/implementation/apple/Tests/GNCBLEMediumTest.m b/internal/platform/implementation/apple/Tests/GNCBLEMediumTest.m index e71f5fc5..47121133 100644 --- a/internal/platform/implementation/apple/Tests/GNCBLEMediumTest.m +++ b/internal/platform/implementation/apple/Tests/GNCBLEMediumTest.m @@ -106,6 +106,36 @@ static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB"; [self waitForExpectations:@[ expectation ] timeout:3]; } +- (void)testStartStopStartScanning { + GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init]; + GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Start scanning."]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID]; + + [medium startScanningForService:serviceUUID + advertisementFoundHandler:^(id peripheral, + NSDictionary *data) { + } + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [medium stopScanningWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); + [medium startScanningForService:serviceUUID + advertisementFoundHandler:^(id peripheral, + NSDictionary *data) { + } + completionHandler:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + }]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:3]; +} + #pragma mark - Decode Advertisement Data - (void)testDecodeAndroidStyleAdvertisementData { @@ -226,6 +256,22 @@ static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB"; [self waitForExpectations:@[ expectation ] timeout:3]; } +- (void)testStopAdvertising { + GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init]; + GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil]; + XCTestExpectation *expectation = + [[XCTestExpectation alloc] initWithDescription:@"Stop advertising."]; + + // Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping + // without having started which tests the code paths relevant to @c GNCBLEMedium. + [medium stopAdvertisingWithCompletionHandler:^(NSError *error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[ expectation ] timeout:3]; +} + #pragma mark - Connect - (void)testSuccessfulConnect { diff --git a/internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.m b/internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.m index 184e5688..297730fa 100644 --- a/internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.m +++ b/internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.m @@ -119,6 +119,7 @@ } - (void)stopAdvertising { + _isAdvertising = false; } #pragma mark - Testing Helpers diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index 2bfc5f9c..049e0a59 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -67,8 +67,6 @@ class BleMedium : public api::ble_v2::BleMedium { bool StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data, api::ble_v2::AdvertiseParameters advertise_set_parameters) override; - // TODO(b/290385712): Not yet implemented. - // // Stops advertising. // // Returns whether or not advertising was successfully stopped. @@ -92,8 +90,6 @@ 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; - // TODO(b/290385712): Not yet implemented. - // // 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 aee27ff9..35d555aa 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -87,8 +87,19 @@ bool BleMedium::StartAdvertising(const api::ble_v2::BleAdvertisementData &advert return blockError == nil; } -// TODO(b/290385712): Implement. -bool BleMedium::StopAdvertising() { return false; } +bool BleMedium::StopAdvertising() { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block NSError *blockError = nil; + [medium_ stopAdvertisingWithCompletionHandler:^(NSError *error) { + if (error != nil) { + GTMLoggerError(@"Failed to stop advertising: %@", error); + } + blockError = error; + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return blockError == nil; +} // TODO(b/290385712): Implement. std::unique_ptr BleMedium::StartScanning( @@ -139,8 +150,19 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLeve return blockError == nil; } -// TODO(b/290385712): Implement. -bool BleMedium::StopScanning() { return false; } +bool BleMedium::StopScanning() { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block NSError *blockError = nil; + [medium_ stopScanningWithCompletionHandler:^(NSError *error) { + if (error != nil) { + GTMLoggerError(@"Failed to stop scanning: %@", error); + } + blockError = error; + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return blockError == nil; +} // TODO(b/290385712): Add implementation that calls ServerGattConnectionCallback methods. std::unique_ptr BleMedium::StartGattServer(