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

Side by Side 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, 6 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/common/physical_web/physical_web_scanner.h"
6
7 #include <string>
8 #include <vector>
9
10 #import <CoreBluetooth/CoreBluetooth.h>
11
12 #include "base/ios/weak_nsobject.h"
13 #include "base/logging.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/macros.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "device/bluetooth/uribeacon/uri_encoder.h"
18 #include "ios/chrome/common/physical_web/physical_web_device.h"
19 #import "ios/chrome/common/physical_web/physical_web_request.h"
20 #include "ios/chrome/common/physical_web/physical_web_types.h"
21
22 namespace {
23
24 NSString* const kUriBeaconServiceUUID = @"FED8";
25 NSString* const kEddystoneBeaconServiceUUID = @"FEAA";
26
27 enum BeaconType {
28 BEACON_TYPE_NONE,
29 BEACON_TYPE_URIBEACON,
30 BEACON_TYPE_EDDYSTONE,
31 };
32
33 } // namespace
34
35 @interface PhysicalWebScanner ()<CBCentralManagerDelegate>
36
37 // Decodes the UriBeacon information in the given |data| and a beacon |type| to
38 // return an unresolved PhysicalWebDevice instance. It also stores the given
39 // |rssi| in the result.
40 + (PhysicalWebDevice*)newDeviceFromData:(NSData*)data
41 rssi:(int)rssi
42 type:(BeaconType)type;
43 // Starts the CoreBluetooth scanner when the bluetooth is powered on.
44 - (void)reallyStart;
45 // Requests metadata of a device if the same URL has not been requested before.
46 - (void)requestMetadataForDevice:(PhysicalWebDevice*)device;
47 // Returns the beacon type given the advertisement data.
48 + (BeaconType)beaconTypeForAdvertisementData:(NSDictionary*)advertisementData;
49
50 @end
51
52 @implementation PhysicalWebScanner {
53 // Delegate that will be notified when the list of devices change.
54 id<PhysicalWebScannerDelegate> delegate_;
55 // The value of |started_| is YES when the scanner has been started and NO
56 // when it's been stopped. The initial value is NO.
57 BOOL started_;
58 // The value is valid when the scanner has been started. If bluetooth is not
59 // powered on, the value is YES, if it's powered on and the CoreBluetooth
60 // scanner has started, the value is NO.
61 BOOL pendingStart_;
62 // List of PhysicalWebRequest that we're waiting the response from.
63 base::scoped_nsobject<NSMutableArray> pendingRequests_;
64 // List of resolved PhysicalWebDevice.
65 base::scoped_nsobject<NSMutableArray> devices_;
66 // List of URLs that have been resolved or have a pending resolution from a
67 // PhysicalWebRequest.
68 base::scoped_nsobject<NSMutableSet> devicesUrls_;
69 // List of final URLs that have been resolved. This set will help us
70 // deduplicate the final URLs.
71 base::scoped_nsobject<NSMutableSet> finalUrls_;
72 // CoreBluetooth scanner.
73 base::scoped_nsobject<CBCentralManager> centralManager_;
74 // The value is YES if network requests can be sent.
75 BOOL networkRequestEnabled_;
76 // List of unresolved PhysicalWebDevice when network requests are not enabled.
77 base::scoped_nsobject<NSMutableArray> unresolvedDevices_;
78 }
79
80 @synthesize networkRequestEnabled = networkRequestEnabled_;
81
82 - (instancetype)initWithDelegate:(id<PhysicalWebScannerDelegate>)delegate {
83 self = [super init];
84 if (self) {
85 delegate_ = delegate;
86 devices_.reset([[NSMutableArray alloc] init]);
87 devicesUrls_.reset([[NSMutableSet alloc] init]);
88 finalUrls_.reset([[NSMutableSet alloc] init]);
89 pendingRequests_.reset([[NSMutableArray alloc] init]);
90 centralManager_.reset([[CBCentralManager alloc]
91 initWithDelegate:self
92 queue:dispatch_get_main_queue()]);
93 unresolvedDevices_.reset([[NSMutableArray alloc] init]);
94 [[NSHTTPCookieStorage sharedHTTPCookieStorage]
95 setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyNever];
96 }
97 return self;
98 }
99
100 - (instancetype)init {
101 NOTREACHED();
102 return nil;
103 }
104
105 - (void)dealloc {
106 [centralManager_ setDelegate:nil];
107 centralManager_.reset();
108 [super dealloc];
109 }
110
111 - (void)start {
112 [self stop];
113 [finalUrls_ removeAllObjects];
114 [devicesUrls_ removeAllObjects];
115 [devices_ removeAllObjects];
116 started_ = YES;
117 if ([centralManager_ state] == CBCentralManagerStatePoweredOn)
118 [self reallyStart];
119 else
120 pendingStart_ = YES;
121 }
122
123 - (void)stop {
124 if (!started_)
125 return;
126 for (PhysicalWebRequest* request in pendingRequests_.get()) {
127 [request cancel];
128 }
129 [pendingRequests_ removeAllObjects];
130 if (!pendingStart_ &&
131 [centralManager_ state] == CBCentralManagerStatePoweredOn) {
132 [centralManager_ stopScan];
133 }
134 pendingStart_ = NO;
135 started_ = NO;
136 }
137
138 - (NSArray*)devices {
139 return [devices_ sortedArrayUsingComparator:^(id obj1, id obj2) {
140 PhysicalWebDevice* device1 = obj1;
141 PhysicalWebDevice* device2 = obj2;
142 // Sorts in ascending order.
143 if ([device1 rank] > [device2 rank]) {
144 return NSOrderedDescending;
145 }
146 if ([device1 rank] < [device2 rank]) {
147 return NSOrderedAscending;
148 }
149 return NSOrderedSame;
150 }];
151 }
152
153 - (void)setNetworkRequestEnabled:(BOOL)enabled {
154 if (networkRequestEnabled_ == enabled) {
155 return;
156 }
157 networkRequestEnabled_ = enabled;
158 if (!networkRequestEnabled_)
159 return;
160
161 // Sends the pending requests.
162 for (PhysicalWebDevice* device in unresolvedDevices_.get()) {
163 [self requestMetadataForDevice:device];
164 }
165 [unresolvedDevices_ removeAllObjects];
166 }
167
168 - (int)unresolvedBeaconsCount {
169 return [unresolvedDevices_ count];
170 }
171
172 - (BOOL)bluetoothEnabled {
173 return [centralManager_ state] == CBCentralManagerStatePoweredOn;
174 }
175
176 - (void)reallyStart {
177 pendingStart_ = NO;
178 NSArray* serviceUUIDs = @[
179 [CBUUID UUIDWithString:kUriBeaconServiceUUID],
180 [CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]
181 ];
182 [centralManager_ scanForPeripheralsWithServices:serviceUUIDs options:nil];
183 }
184
185 #pragma mark -
186 #pragma mark CBCentralManagerDelegate methods
187
188 - (void)centralManagerDidUpdateState:(CBCentralManager*)central {
189 if ([centralManager_ state] == CBCentralManagerStatePoweredOn) {
190 if (pendingStart_)
191 [self reallyStart];
192 } else {
193 if (started_ && !pendingStart_) {
194 pendingStart_ = YES;
195 [centralManager_ stopScan];
196 }
197 }
198 [delegate_ scannerBluetoothStatusUpdated:self];
199 }
200
201 + (BeaconType)beaconTypeForAdvertisementData:(NSDictionary*)advertisementData {
202 NSDictionary* serviceData =
203 [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
204 if ([serviceData objectForKey:[CBUUID UUIDWithString:kUriBeaconServiceUUID]])
205 return BEACON_TYPE_URIBEACON;
206 if ([serviceData
207 objectForKey:[CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]])
208 return BEACON_TYPE_EDDYSTONE;
209 return BEACON_TYPE_NONE;
210 }
211
212 - (void)centralManager:(CBCentralManager*)central
213 didDiscoverPeripheral:(CBPeripheral*)peripheral
214 advertisementData:(NSDictionary*)advertisementData
215 RSSI:(NSNumber*)RSSI {
216 BeaconType type =
217 [PhysicalWebScanner beaconTypeForAdvertisementData:advertisementData];
218 if (type == BEACON_TYPE_NONE)
219 return;
220
221 NSDictionary* serviceData =
222 [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
223 NSData* data = nil;
224 switch (type) {
225 case BEACON_TYPE_URIBEACON:
226 data = [serviceData
227 objectForKey:[CBUUID UUIDWithString:kUriBeaconServiceUUID]];
228 break;
229 case BEACON_TYPE_EDDYSTONE:
230 data = [serviceData
231 objectForKey:[CBUUID UUIDWithString:kEddystoneBeaconServiceUUID]];
232 break;
233 default:
234 // Do nothing.
235 break;
236 }
237 DCHECK(data);
238
239 base::scoped_nsobject<PhysicalWebDevice> device([PhysicalWebScanner
240 newDeviceFromData:data
241 rssi:[RSSI intValue]
242 type:type]);
243 // Skip if the data couldn't be parsed.
244 if (!device.get())
245 return;
246
247 // Skip if the URL has already been seen.
248 if ([devicesUrls_ containsObject:[device url]])
249 return;
250 [devicesUrls_ addObject:[device url]];
251
252 if (networkRequestEnabled_) {
253 [self requestMetadataForDevice:device];
254 } else {
255 [unresolvedDevices_ addObject:device];
256 [delegate_ scannerUpdatedDevices:self];
257 }
258 }
259
260 #pragma mark -
261 #pragma mark UriBeacon resolution
262
263 + (PhysicalWebDevice*)newDeviceFromData:(NSData*)data
264 rssi:(int)rssi
265 type:(BeaconType)type {
266 // No UriBeacon service data.
267 if (!data)
268 return nil;
269 // UriBeacon service data too small.
270 if ([data length] <= 2)
271 return nil;
272
273 const uint8_t* bytes = static_cast<const uint8_t*>([data bytes]);
274 if (type == BEACON_TYPE_EDDYSTONE) {
275 // The packet type is encoded in the high-order 4 bits.
276 // Returns if it's not an Eddystone-URL.
277 if ((bytes[0] & 0xf0) != 0x10)
278 return nil;
279 }
280
281 // - transmit power is at offset 1
282 // TX Power in the UriBeacon advertising packet is the received power at 0
283 // meters. The Transmit Power Level represents the transmit power level in
284 // dBm, and the value ranges from -100 dBm to +20 dBm to a resolution of 1
285 // dBm.
286 int transmitPower = static_cast<char>(bytes[1]);
287 // - scheme and URL are at offset 2.
288 std::vector<uint8_t> encodedURI(&bytes[2], &bytes[[data length]]);
289 std::string utf8URI;
290 device::DecodeUriBeaconUri(encodedURI, utf8URI);
291 NSString* uriString = base::SysUTF8ToNSString(utf8URI);
292 return [[PhysicalWebDevice alloc] initWithURL:[NSURL URLWithString:uriString]
293 requestURL:nil
294 icon:nil
295 title:nil
296 description:nil
297 transmitPower:transmitPower
298 rssi:rssi
299 rank:physical_web::kMaxRank];
300 }
301
302 - (void)requestMetadataForDevice:(PhysicalWebDevice*)device {
303 base::scoped_nsobject<PhysicalWebRequest> request(
304 [[PhysicalWebRequest alloc] initWithDevice:device]);
305 PhysicalWebRequest* strongRequest = request.get();
306 [pendingRequests_ addObject:strongRequest];
307 base::WeakNSObject<PhysicalWebScanner> weakSelf(self);
308 [request start:^(PhysicalWebDevice* device, NSError* error) {
309 base::scoped_nsobject<PhysicalWebScanner> strongSelf([weakSelf retain]);
310 if (!strongSelf) {
311 return;
312 }
313 // ignore if there's an error.
314 if (!error) {
315 if (![strongSelf.get()->finalUrls_ containsObject:[device url]]) {
316 [strongSelf.get()->devices_ addObject:device];
317 [strongSelf.get()->delegate_ scannerUpdatedDevices:weakSelf];
318 [strongSelf.get()->finalUrls_ addObject:[device url]];
319 }
320 }
321 [strongSelf.get()->pendingRequests_ removeObject:strongRequest];
322 }];
323 }
324
325 @end
OLDNEW
« 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