Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(49)

Unified Diff: ios/chrome/common/physical_web/physical_web_scanner.mm

Issue 2023833002: [iOS] Upstream physical web classes. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add comment about limiting the dependencies Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..3b947a3f6e9498a54a9a66402e535862f2b02e34
--- /dev/null
+++ b/ios/chrome/common/physical_web/physical_web_scanner.mm
@@ -0,0 +1,325 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/common/physical_web/physical_web_scanner.h"
+
+#include <string>
+#include <vector>
+
+#import <CoreBluetooth/CoreBluetooth.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/macros.h"
+#include "base/strings/sys_string_conversions.h"
+#include "device/bluetooth/uribeacon/uri_encoder.h"
+#include "ios/chrome/common/physical_web/physical_web_device.h"
+#import "ios/chrome/common/physical_web/physical_web_request.h"
+#include "ios/chrome/common/physical_web/physical_web_types.h"
+
+namespace {
+
+NSString* const kUriBeaconServiceUUID = @"FED8";
+NSString* const kEddystoneBeaconServiceUUID = @"FEAA";
+
+enum BeaconType {
+ BEACON_TYPE_NONE,
+ BEACON_TYPE_URIBEACON,
+ BEACON_TYPE_EDDYSTONE,
+};
+
+} // namespace
+
+@interface PhysicalWebScanner ()<CBCentralManagerDelegate>
+
+// Decodes the UriBeacon information in the given |data| and a beacon |type| to
+// return an unresolved PhysicalWebDevice instance. It also stores the given
+// |rssi| in the result.
++ (PhysicalWebDevice*)newDeviceFromData:(NSData*)data
+ rssi:(int)rssi
+ type:(BeaconType)type;
+// Starts the CoreBluetooth scanner when the bluetooth is powered on.
+- (void)reallyStart;
+// 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.
++ (BeaconType)beaconTypeForAdvertisementData:(NSDictionary*)advertisementData;
+
+@end
+
+@implementation PhysicalWebScanner {
+ // Delegate that will be notified when the list of devices change.
+ id<PhysicalWebScannerDelegate> delegate_;
+ // The value of |started_| is YES when the scanner has been started and NO
+ // when it's been stopped. The initial value is NO.
+ BOOL started_;
+ // The value is valid when the scanner has been started. If bluetooth is not
+ // powered on, the value is YES, if it's powered on and the CoreBluetooth
+ // scanner has started, the value is NO.
+ BOOL pendingStart_;
+ // List of PhysicalWebRequest that we're waiting the response from.
+ base::scoped_nsobject<NSMutableArray> pendingRequests_;
+ // List of resolved PhysicalWebDevice.
+ base::scoped_nsobject<NSMutableArray> devices_;
+ // List of URLs that have been resolved or have a pending resolution from a
+ // PhysicalWebRequest.
+ base::scoped_nsobject<NSMutableSet> devicesUrls_;
+ // List of final URLs that have been resolved. This set will help us
+ // deduplicate the final URLs.
+ base::scoped_nsobject<NSMutableSet> finalUrls_;
+ // CoreBluetooth scanner.
+ base::scoped_nsobject<CBCentralManager> centralManager_;
+ // 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_;
+}
+
+@synthesize networkRequestEnabled = networkRequestEnabled_;
+
+- (instancetype)initWithDelegate:(id<PhysicalWebScannerDelegate>)delegate {
+ self = [super init];
+ if (self) {
+ delegate_ = delegate;
+ devices_.reset([[NSMutableArray alloc] init]);
+ devicesUrls_.reset([[NSMutableSet alloc] init]);
+ finalUrls_.reset([[NSMutableSet alloc] init]);
+ pendingRequests_.reset([[NSMutableArray alloc] init]);
+ centralManager_.reset([[CBCentralManager alloc]
+ initWithDelegate:self
+ queue:dispatch_get_main_queue()]);
+ unresolvedDevices_.reset([[NSMutableArray alloc] init]);
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage]
+ setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyNever];
+ }
+ return self;
+}
+
+- (instancetype)init {
+ NOTREACHED();
+ return nil;
+}
+
+- (void)dealloc {
+ [centralManager_ setDelegate:nil];
+ centralManager_.reset();
+ [super dealloc];
+}
+
+- (void)start {
+ [self stop];
+ [finalUrls_ removeAllObjects];
+ [devicesUrls_ removeAllObjects];
+ [devices_ removeAllObjects];
+ started_ = YES;
+ if ([centralManager_ state] == CBCentralManagerStatePoweredOn)
+ [self reallyStart];
+ else
+ pendingStart_ = YES;
+}
+
+- (void)stop {
+ if (!started_)
+ return;
+ for (PhysicalWebRequest* request in pendingRequests_.get()) {
+ [request cancel];
+ }
+ [pendingRequests_ removeAllObjects];
+ if (!pendingStart_ &&
+ [centralManager_ state] == CBCentralManagerStatePoweredOn) {
+ [centralManager_ stopScan];
+ }
+ pendingStart_ = NO;
+ started_ = NO;
+}
+
+- (NSArray*)devices {
+ return [devices_ sortedArrayUsingComparator:^(id obj1, id obj2) {
+ PhysicalWebDevice* device1 = obj1;
+ PhysicalWebDevice* device2 = obj2;
+ // Sorts in ascending order.
+ if ([device1 rank] > [device2 rank]) {
+ return NSOrderedDescending;
+ }
+ if ([device1 rank] < [device2 rank]) {
+ return NSOrderedAscending;
+ }
+ return NSOrderedSame;
+ }];
+}
+
+- (void)setNetworkRequestEnabled:(BOOL)enabled {
+ if (networkRequestEnabled_ == enabled) {
+ return;
+ }
+ networkRequestEnabled_ = enabled;
+ if (!networkRequestEnabled_)
+ return;
+
+ // Sends the pending requests.
+ for (PhysicalWebDevice* device in unresolvedDevices_.get()) {
+ [self requestMetadataForDevice:device];
+ }
+ [unresolvedDevices_ removeAllObjects];
+}
+
+- (int)unresolvedBeaconsCount {
+ return [unresolvedDevices_ count];
+}
+
+- (BOOL)bluetoothEnabled {
+ return [centralManager_ state] == CBCentralManagerStatePoweredOn;
+}
+
+- (void)reallyStart {
+ pendingStart_ = NO;
+ NSArray* serviceUUIDs = @[
+ [CBUUID UUIDWithString:kUriBeaconServiceUUID],
+ [CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]
+ ];
+ [centralManager_ scanForPeripheralsWithServices:serviceUUIDs options:nil];
+}
+
+#pragma mark -
+#pragma mark CBCentralManagerDelegate methods
+
+- (void)centralManagerDidUpdateState:(CBCentralManager*)central {
+ if ([centralManager_ state] == CBCentralManagerStatePoweredOn) {
+ if (pendingStart_)
+ [self reallyStart];
+ } else {
+ if (started_ && !pendingStart_) {
+ pendingStart_ = YES;
+ [centralManager_ stopScan];
+ }
+ }
+ [delegate_ scannerBluetoothStatusUpdated:self];
+}
+
++ (BeaconType)beaconTypeForAdvertisementData:(NSDictionary*)advertisementData {
+ NSDictionary* serviceData =
+ [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
+ if ([serviceData objectForKey:[CBUUID UUIDWithString:kUriBeaconServiceUUID]])
+ return BEACON_TYPE_URIBEACON;
+ if ([serviceData
+ objectForKey:[CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]])
+ return BEACON_TYPE_EDDYSTONE;
+ return BEACON_TYPE_NONE;
+}
+
+- (void)centralManager:(CBCentralManager*)central
+ didDiscoverPeripheral:(CBPeripheral*)peripheral
+ advertisementData:(NSDictionary*)advertisementData
+ RSSI:(NSNumber*)RSSI {
+ BeaconType type =
+ [PhysicalWebScanner beaconTypeForAdvertisementData:advertisementData];
+ if (type == BEACON_TYPE_NONE)
+ return;
+
+ NSDictionary* serviceData =
+ [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
+ NSData* data = nil;
+ switch (type) {
+ case BEACON_TYPE_URIBEACON:
+ data = [serviceData
+ objectForKey:[CBUUID UUIDWithString:kUriBeaconServiceUUID]];
+ break;
+ case BEACON_TYPE_EDDYSTONE:
+ data = [serviceData
+ objectForKey:[CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]];
+ break;
+ default:
+ // Do nothing.
+ break;
+ }
+ DCHECK(data);
+
+ base::scoped_nsobject<PhysicalWebDevice> device([PhysicalWebScanner
+ newDeviceFromData:data
+ rssi:[RSSI intValue]
+ type:type]);
+ // Skip if the data couldn't be parsed.
+ if (!device.get())
+ return;
+
+ // Skip if the URL has already been seen.
+ if ([devicesUrls_ containsObject:[device url]])
+ return;
+ [devicesUrls_ addObject:[device url]];
+
+ if (networkRequestEnabled_) {
+ [self requestMetadataForDevice:device];
+ } else {
+ [unresolvedDevices_ addObject:device];
+ [delegate_ scannerUpdatedDevices:self];
+ }
+}
+
+#pragma mark -
+#pragma mark UriBeacon resolution
+
++ (PhysicalWebDevice*)newDeviceFromData:(NSData*)data
+ rssi:(int)rssi
+ type:(BeaconType)type {
+ // No UriBeacon service data.
+ if (!data)
+ return nil;
+ // UriBeacon service data too small.
+ if ([data length] <= 2)
+ return nil;
+
+ const uint8_t* bytes = static_cast<const uint8_t*>([data bytes]);
+ if (type == BEACON_TYPE_EDDYSTONE) {
+ // The packet type is encoded in the high-order 4 bits.
+ // Returns if it's not an Eddystone-URL.
+ if ((bytes[0] & 0xf0) != 0x10)
+ return nil;
+ }
+
+ // - transmit power is at offset 1
+ // TX Power in the UriBeacon advertising packet is the received power at 0
+ // meters. The Transmit Power Level represents the transmit power level in
+ // dBm, and the value ranges from -100 dBm to +20 dBm to a resolution of 1
+ // dBm.
+ int transmitPower = static_cast<char>(bytes[1]);
+ // - scheme and URL are at offset 2.
+ std::vector<uint8_t> encodedURI(&bytes[2], &bytes[[data length]]);
+ std::string utf8URI;
+ device::DecodeUriBeaconUri(encodedURI, utf8URI);
+ NSString* uriString = base::SysUTF8ToNSString(utf8URI);
+ return [[PhysicalWebDevice alloc] initWithURL:[NSURL URLWithString:uriString]
+ requestURL:nil
+ icon:nil
+ title:nil
+ description:nil
+ transmitPower:transmitPower
+ rssi:rssi
+ rank:physical_web::kMaxRank];
+}
+
+- (void)requestMetadataForDevice:(PhysicalWebDevice*)device {
+ base::scoped_nsobject<PhysicalWebRequest> request(
+ [[PhysicalWebRequest alloc] initWithDevice:device]);
+ PhysicalWebRequest* strongRequest = request.get();
+ [pendingRequests_ addObject:strongRequest];
+ base::WeakNSObject<PhysicalWebScanner> weakSelf(self);
+ [request start:^(PhysicalWebDevice* device, NSError* error) {
+ base::scoped_nsobject<PhysicalWebScanner> strongSelf([weakSelf retain]);
+ if (!strongSelf) {
+ return;
+ }
+ // ignore if there's an error.
+ if (!error) {
+ if (![strongSelf.get()->finalUrls_ containsObject:[device url]]) {
+ [strongSelf.get()->devices_ addObject:device];
+ [strongSelf.get()->delegate_ scannerUpdatedDevices:weakSelf];
+ [strongSelf.get()->finalUrls_ addObject:[device url]];
+ }
+ }
+ [strongSelf.get()->pendingRequests_ removeObject:strongRequest];
+ }];
+}
+
+@end
« no previous file with comments | « ios/chrome/common/physical_web/physical_web_scanner.h ('k') | ios/chrome/common/physical_web/physical_web_types.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698