Chromium Code Reviews| Index: ios/chrome/common/physical_web/physical_web_scanner.mm |
| diff --git a/ios/chrome/common/physical_web/physical_web_scanner.mm b/ios/chrome/common/physical_web/physical_web_scanner.mm |
| index 02c5d0c0c03d6f1fb5c979a23eb6c11217c30bd8..2a62f23c829da8ceea6ad936d3ce0daf5bb87ec7 100644 |
| --- a/ios/chrome/common/physical_web/physical_web_scanner.mm |
| +++ b/ios/chrome/common/physical_web/physical_web_scanner.mm |
| @@ -25,6 +25,12 @@ namespace { |
| NSString* const kUriBeaconServiceUUID = @"FED8"; |
| NSString* const kEddystoneBeaconServiceUUID = @"FEAA"; |
| +// The length of time in seconds since a URL was last seen before it should be |
| +// considered lost (ie, no longer nearby). |
| +const NSTimeInterval kLostThresholdSeconds = 15.0; |
| +// The time interval in seconds between checks for lost URLs. |
| +const NSTimeInterval kUpdateIntervalSeconds = 6.0; |
| + |
| enum BeaconType { |
| BEACON_TYPE_NONE, |
| BEACON_TYPE_URIBEACON, |
| @@ -41,8 +47,14 @@ enum BeaconType { |
| + (PhysicalWebDevice*)newDeviceFromData:(NSData*)data |
| rssi:(int)rssi |
| type:(BeaconType)type; |
| -// Starts the CoreBluetooth scanner when the bluetooth is powered on. |
| +// Starts the CoreBluetooth scanner when the bluetooth is powered on and starts |
| +// the update timer. |
| - (void)reallyStart; |
| +// Stops the CoreBluetooth scanner and update timer. |
| +- (void)reallyStop; |
| +// Timer callback to check for lost URLs based on the elapsed time since they |
| +// were last seen. |
| +- (void)onUpdateTimeElapsed:(NSTimer*)timer; |
| // Requests metadata of a device if the same URL has not been requested before. |
| - (void)requestMetadataForDevice:(PhysicalWebDevice*)device; |
| // Returns the beacon type given the advertisement data. |
| @@ -72,12 +84,19 @@ enum BeaconType { |
| base::scoped_nsobject<NSMutableSet> finalUrls_; |
| // CoreBluetooth scanner. |
| base::scoped_nsobject<CBCentralManager> centralManager_; |
| + // When YES, we will notify the delegate if a previously nearby URL is lost |
| + // and remove it from the list of nearby devices. |
| + BOOL onLostDetectionEnabled_; |
| // The value is YES if network requests can be sent. |
| BOOL networkRequestEnabled_; |
| // List of unresolved PhysicalWebDevice when network requests are not enabled. |
| base::scoped_nsobject<NSMutableArray> unresolvedDevices_; |
| + // A repeating timer to check for lost URLs. If the elapsed time since an URL |
| + // was last seen exceeds a threshold, the URL is considered lost. |
| + base::scoped_nsobject<NSTimer> updateTimer_; |
| } |
| +@synthesize onLostDetectionEnabled = onLostDetectionEnabled_; |
| @synthesize networkRequestEnabled = networkRequestEnabled_; |
| - (instancetype)initWithDelegate:(id<PhysicalWebScannerDelegate>)delegate { |
| @@ -106,6 +125,10 @@ enum BeaconType { |
| - (void)dealloc { |
| [centralManager_ setDelegate:nil]; |
| centralManager_.reset(); |
| + if (updateTimer_.get()) { |
| + [updateTimer_ invalidate]; |
| + updateTimer_.reset(); |
| + } |
| [super dealloc]; |
| } |
| @@ -129,7 +152,7 @@ enum BeaconType { |
| } |
| [pendingRequests_ removeAllObjects]; |
| if (!pendingStart_ && [self bluetoothEnabled]) { |
| - [centralManager_ stopScan]; |
| + [self reallyStop]; |
| } |
| pendingStart_ = NO; |
| started_ = NO; |
| @@ -189,6 +212,16 @@ enum BeaconType { |
| [unresolvedDevices_ removeAllObjects]; |
| } |
| +- (void)setOnLostDetectionEnabled:(BOOL)enabled { |
| + if (enabled == onLostDetectionEnabled_) { |
| + return; |
| + } |
| + onLostDetectionEnabled_ = enabled; |
| + if (started_) { |
| + [self start]; |
| + } |
| +} |
| + |
| - (int)unresolvedBeaconsCount { |
| return [unresolvedDevices_ count]; |
| } |
| @@ -205,10 +238,89 @@ enum BeaconType { |
| - (void)reallyStart { |
| pendingStart_ = NO; |
| + |
| + if (updateTimer_.get()) { |
| + [updateTimer_ invalidate]; |
| + updateTimer_.reset(); |
| + } |
| + |
| NSArray* serviceUUIDs = @[ |
| [CBUUID UUIDWithString:kUriBeaconServiceUUID], |
| [CBUUID UUIDWithString:kEddystoneBeaconServiceUUID] |
| ]; |
| + if (onLostDetectionEnabled_) { |
| + // Register a repeating timer to periodically check for lost URLs. |
| + updateTimer_.reset([NSTimer |
| + scheduledTimerWithTimeInterval:kUpdateIntervalSeconds |
| + target:self |
| + selector:@selector(onUpdateTimeElapsed:) |
| + userInfo:nil |
| + repeats:YES]); |
| + } |
| + [centralManager_ scanForPeripheralsWithServices:serviceUUIDs options:nil]; |
| +} |
| + |
| +- (void)reallyStop { |
| + if (updateTimer_.get()) { |
| + [updateTimer_ invalidate]; |
| + updateTimer_.reset(); |
| + } |
| + |
| + [centralManager_ stopScan]; |
| +} |
| + |
| +- (void)onUpdateTimeElapsed:(NSTimer*)timer { |
| + NSDate* now = [NSDate date]; |
| + NSMutableArray* lostDevices = [NSMutableArray array]; |
| + NSMutableArray* lostUnresolvedDevices = [NSMutableArray array]; |
| + NSMutableArray* lostScannedUrls = [NSMutableArray array]; |
| + |
| + for (PhysicalWebDevice* device in [self devices]) { |
|
Olivier
2016/10/21 09:22:13
You don't need to sort here. May be use devices_.g
mattreynolds
2016/10/21 18:16:05
Done.
|
| + NSDate* scanTimestamp = [device scanTimestamp]; |
| + NSTimeInterval elapsedSeconds = [now timeIntervalSinceDate:scanTimestamp]; |
| + if (elapsedSeconds > kLostThresholdSeconds) { |
| + [lostDevices addObject:device]; |
| + [lostScannedUrls addObject:[device requestURL]]; |
| + [devicesUrls_ removeObject:[device requestURL]]; |
| + [finalUrls_ removeObject:[device url]]; |
| + } |
| + } |
| + |
| + for (PhysicalWebDevice* device in unresolvedDevices_.get()) { |
| + NSDate* scanTimestamp = [device scanTimestamp]; |
| + NSTimeInterval elapsedSeconds = [now timeIntervalSinceDate:scanTimestamp]; |
| + if (elapsedSeconds > kLostThresholdSeconds) { |
| + [lostUnresolvedDevices addObject:device]; |
| + [lostScannedUrls addObject:[device requestURL]]; |
| + [devicesUrls_ removeObject:[device requestURL]]; |
| + } |
| + } |
| + |
| + NSMutableArray* requestsToRemove = [NSMutableArray array]; |
| + for (PhysicalWebRequest* request in pendingRequests_.get()) { |
| + if ([lostScannedUrls containsObject:[request requestURL]]) { |
| + [request cancel]; |
| + [requestsToRemove addObject:request]; |
| + } |
| + } |
| + |
| + [devices_ removeObjectsInArray:lostDevices]; |
| + [unresolvedDevices_ removeObjectsInArray:lostUnresolvedDevices]; |
| + [pendingRequests_ removeObjectsInArray:requestsToRemove]; |
| + |
| + if ([lostDevices count]) { |
| + [delegate_ scannerUpdatedDevices:self]; |
| + } |
| + |
| + // TODO(crbug.com/657056): Remove this workaround when radar is fixed. |
| + // For unknown reasons, when scanning for longer periods (on the order of |
| + // minutes), the scanner is less reliable at detecting all nearby URLs. As a |
| + // workaround, we restart the scanner each time we check for lost URLs. |
| + NSArray* serviceUUIDs = @[ |
| + [CBUUID UUIDWithString:kUriBeaconServiceUUID], |
| + [CBUUID UUIDWithString:kEddystoneBeaconServiceUUID] |
| + ]; |
| + [centralManager_ stopScan]; |
| [centralManager_ scanForPeripheralsWithServices:serviceUUIDs options:nil]; |
| } |
| @@ -222,7 +334,7 @@ enum BeaconType { |
| } else { |
| if (started_ && !pendingStart_) { |
| pendingStart_ = YES; |
| - [centralManager_ stopScan]; |
| + [self reallyStop]; |
| } |
| } |
| [delegate_ scannerBluetoothStatusUpdated:self]; |
| @@ -274,10 +386,23 @@ enum BeaconType { |
| if (!device.get()) |
| return; |
| - // Skip if the URL has already been seen. |
| - if ([devicesUrls_ containsObject:[device url]]) |
| + // If the URL has already been seen, update its timestamp. |
| + if ([devicesUrls_ containsObject:[device requestURL]]) { |
| + for (PhysicalWebDevice* unresolvedDevice in unresolvedDevices_.get()) { |
| + if ([[unresolvedDevice requestURL] isEqual:[device requestURL]]) { |
| + [unresolvedDevice setScanTimestamp:[NSDate date]]; |
|
Olivier
2016/10/21 09:22:14
break;
or return; ?
mattreynolds
2016/10/21 18:16:05
Done.
|
| + } |
| + } |
| + for (PhysicalWebDevice* resolvedDevice in devices_.get()) { |
| + if ([[resolvedDevice requestURL] isEqual:[device requestURL]]) { |
| + [resolvedDevice setScanTimestamp:[NSDate date]]; |
| + } |
| + } |
| return; |
| - [devicesUrls_ addObject:[device url]]; |
| + } |
| + |
| + [device setScanTimestamp:[NSDate date]]; |
| + [devicesUrls_ addObject:[device requestURL]]; |
| if (networkRequestEnabled_) { |
| [self requestMetadataForDevice:device]; |
| @@ -326,13 +451,14 @@ enum BeaconType { |
| return nil; |
| return [[PhysicalWebDevice alloc] initWithURL:url |
| - requestURL:nil |
| + requestURL:url |
| icon:nil |
| title:nil |
| description:nil |
| transmitPower:transmitPower |
| rssi:rssi |
| - rank:physical_web::kMaxRank]; |
| + rank:physical_web::kMaxRank |
| + scanTimestamp:[NSDate date]]; |
| } |
| - (void)requestMetadataForDevice:(PhysicalWebDevice*)device { |