Add OWNERS to content/browser/quota
[chromium-blink-merge.git] / device / bluetooth / bluetooth_adapter_mac.mm
blob9a2875d1760b18828941bd9510069fffbcac41b8
1 // Copyright 2013 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.
5 #include "device/bluetooth/bluetooth_adapter_mac.h"
7 #import <IOBluetooth/objc/IOBluetoothDevice.h>
8 #import <IOBluetooth/objc/IOBluetoothHostController.h>
10 #include <string>
12 #include "base/bind.h"
13 #include "base/compiler_specific.h"
14 #include "base/containers/hash_tables.h"
15 #include "base/location.h"
16 #include "base/mac/mac_util.h"
17 #include "base/mac/sdk_forward_declarations.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/profiler/scoped_tracker.h"
20 #include "base/sequenced_task_runner.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/strings/sys_string_conversions.h"
23 #include "base/thread_task_runner_handle.h"
24 #include "base/time/time.h"
25 #include "device/bluetooth/bluetooth_classic_device_mac.h"
26 #include "device/bluetooth/bluetooth_discovery_session.h"
27 #include "device/bluetooth/bluetooth_discovery_session_outcome.h"
28 #include "device/bluetooth/bluetooth_low_energy_central_manager_delegate.h"
29 #include "device/bluetooth/bluetooth_socket_mac.h"
30 #include "device/bluetooth/bluetooth_uuid.h"
32 namespace {
34 // The frequency with which to poll the adapter for updates.
35 const int kPollIntervalMs = 500;
37 }  // namespace
39 namespace device {
41 // static
42 const NSTimeInterval BluetoothAdapterMac::kDiscoveryTimeoutSec =
43     180;  // 3 minutes
45 // static
46 base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
47     const InitCallback& init_callback) {
48   return BluetoothAdapterMac::CreateAdapter();
51 // static
52 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapter() {
53   BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
54   adapter->Init();
55   return adapter->weak_ptr_factory_.GetWeakPtr();
58 // static
59 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapterForTest(
60     std::string name,
61     std::string address,
62     scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
63   BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
64   adapter->InitForTest(ui_task_runner);
65   adapter->name_ = name;
66   adapter->address_ = address;
67   return adapter->weak_ptr_factory_.GetWeakPtr();
70 BluetoothAdapterMac::BluetoothAdapterMac()
71     : BluetoothAdapter(),
72       classic_powered_(false),
73       num_discovery_sessions_(0),
74       classic_discovery_manager_(
75           BluetoothDiscoveryManagerMac::CreateClassic(this)),
76       weak_ptr_factory_(this) {
77   if (IsLowEnergyAvailable()) {
78     low_energy_discovery_manager_.reset(
79         BluetoothLowEnergyDiscoveryManagerMac::Create(this));
80     low_energy_central_manager_delegate_.reset(
81         [[BluetoothLowEnergyCentralManagerDelegate alloc]
82             initWithDiscoveryManager:low_energy_discovery_manager_.get()
83                           andAdapter:this]);
84     Class aClass = NSClassFromString(@"CBCentralManager");
85     low_energy_central_manager_.reset([[aClass alloc]
86         initWithDelegate:low_energy_central_manager_delegate_.get()
87                    queue:dispatch_get_main_queue()]);
88     low_energy_discovery_manager_->SetCentralManager(
89         low_energy_central_manager_.get());
90   }
91   DCHECK(classic_discovery_manager_.get());
94 BluetoothAdapterMac::~BluetoothAdapterMac() {
97 std::string BluetoothAdapterMac::GetAddress() const {
98   return address_;
101 std::string BluetoothAdapterMac::GetName() const {
102   return name_;
105 void BluetoothAdapterMac::SetName(const std::string& name,
106                                   const base::Closure& callback,
107                                   const ErrorCallback& error_callback) {
108   NOTIMPLEMENTED();
111 bool BluetoothAdapterMac::IsInitialized() const {
112   return true;
115 bool BluetoothAdapterMac::IsPresent() const {
116   bool is_present = !address_.empty();
117   if (IsLowEnergyAvailable()) {
118     is_present = is_present || ([low_energy_central_manager_ state] ==
119                                 CBCentralManagerStatePoweredOn);
120   }
121   return is_present;
124 bool BluetoothAdapterMac::IsPowered() const {
125   bool is_powered = classic_powered_;
126   if (IsLowEnergyAvailable()) {
127     is_powered = is_powered || ([low_energy_central_manager_ state] ==
128                                 CBCentralManagerStatePoweredOn);
129   }
130   return is_powered;
133 void BluetoothAdapterMac::SetPowered(bool powered,
134                                      const base::Closure& callback,
135                                      const ErrorCallback& error_callback) {
136   NOTIMPLEMENTED();
139 // TODO(krstnmnlsn): If this information is retrievable form IOBluetooth we
140 // should return the discoverable status.
141 bool BluetoothAdapterMac::IsDiscoverable() const {
142   return false;
145 void BluetoothAdapterMac::SetDiscoverable(
146     bool discoverable,
147     const base::Closure& callback,
148     const ErrorCallback& error_callback) {
149   NOTIMPLEMENTED();
152 bool BluetoothAdapterMac::IsDiscovering() const {
153   bool is_discovering = classic_discovery_manager_->IsDiscovering();
154   if (IsLowEnergyAvailable())
155     is_discovering =
156         is_discovering || low_energy_discovery_manager_->IsDiscovering();
157   return is_discovering;
160 void BluetoothAdapterMac::CreateRfcommService(
161     const BluetoothUUID& uuid,
162     const ServiceOptions& options,
163     const CreateServiceCallback& callback,
164     const CreateServiceErrorCallback& error_callback) {
165   scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
166   socket->ListenUsingRfcomm(
167       this, uuid, options, base::Bind(callback, socket), error_callback);
170 void BluetoothAdapterMac::CreateL2capService(
171     const BluetoothUUID& uuid,
172     const ServiceOptions& options,
173     const CreateServiceCallback& callback,
174     const CreateServiceErrorCallback& error_callback) {
175   scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
176   socket->ListenUsingL2cap(
177       this, uuid, options, base::Bind(callback, socket), error_callback);
180 void BluetoothAdapterMac::RegisterAudioSink(
181     const BluetoothAudioSink::Options& options,
182     const AcquiredCallback& callback,
183     const BluetoothAudioSink::ErrorCallback& error_callback) {
184   NOTIMPLEMENTED();
185   error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM);
188 void BluetoothAdapterMac::RegisterAdvertisement(
189     scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
190     const CreateAdvertisementCallback& callback,
191     const CreateAdvertisementErrorCallback& error_callback) {
192   NOTIMPLEMENTED();
193   error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
196 void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) {
197   ClassicDeviceAdded(device);
200 void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) {
201   if (unexpected) {
202     DVLOG(1) << "Discovery stopped unexpectedly";
203     num_discovery_sessions_ = 0;
204     MarkDiscoverySessionsAsInactive();
205   }
206   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
207                     observers_,
208                     AdapterDiscoveringChanged(this, false));
211 void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) {
212   // TODO(isherman): Investigate whether this method can be replaced with a call
213   // to +registerForConnectNotifications:selector:.
214   DVLOG(1) << "Adapter registered a new connection from device with address: "
215            << BluetoothClassicDeviceMac::GetDeviceAddress(device);
216   ClassicDeviceAdded(device);
219 // static
220 bool BluetoothAdapterMac::IsLowEnergyAvailable() {
221   return base::mac::IsOSYosemiteOrLater();
224 void BluetoothAdapterMac::SetCentralManagerForTesting(
225     CBCentralManager* central_manager) {
226   CHECK(BluetoothAdapterMac::IsLowEnergyAvailable());
227   [central_manager performSelector:@selector(setDelegate:)
228                         withObject:low_energy_central_manager_delegate_];
229   low_energy_central_manager_.reset(central_manager);
230   low_energy_discovery_manager_->SetCentralManager(
231       low_energy_central_manager_.get());
234 void BluetoothAdapterMac::RemovePairingDelegateInternal(
235     BluetoothDevice::PairingDelegate* pairing_delegate) {
238 void BluetoothAdapterMac::AddDiscoverySession(
239     BluetoothDiscoveryFilter* discovery_filter,
240     const base::Closure& callback,
241     const DiscoverySessionErrorCallback& error_callback) {
242   DVLOG(1) << __func__;
243   if (num_discovery_sessions_ > 0) {
244     DCHECK(IsDiscovering());
245     num_discovery_sessions_++;
246     // We are already running a discovery session, notify the system if the
247     // filter has changed.
248     if (!StartDiscovery(discovery_filter)) {
249       // TODO: Provide a more precise error here.
250       error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN);
251       return;
252     }
253     callback.Run();
254     return;
255   }
257   DCHECK_EQ(0, num_discovery_sessions_);
259   if (!StartDiscovery(discovery_filter)) {
260     // TODO: Provide a more precise error here.
261     error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN);
262     return;
263   }
265   DVLOG(1) << "Added a discovery session";
266   num_discovery_sessions_++;
267   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
268                     observers_,
269                     AdapterDiscoveringChanged(this, true));
270   callback.Run();
273 void BluetoothAdapterMac::RemoveDiscoverySession(
274     BluetoothDiscoveryFilter* discovery_filter,
275     const base::Closure& callback,
276     const DiscoverySessionErrorCallback& error_callback) {
277   DVLOG(1) << __func__;
279   if (num_discovery_sessions_ > 1) {
280     // There are active sessions other than the one currently being removed.
281     DCHECK(IsDiscovering());
282     num_discovery_sessions_--;
283     callback.Run();
284     return;
285   }
287   if (num_discovery_sessions_ == 0) {
288     DVLOG(1) << "No active discovery sessions. Returning error.";
289     error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE);
290     return;
291   }
293   // Default to dual discovery if |discovery_filter| is NULL.
294   BluetoothDiscoveryFilter::TransportMask transport =
295       BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
296   if (discovery_filter)
297     transport = discovery_filter->GetTransport();
299   if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) {
300     if (!classic_discovery_manager_->StopDiscovery()) {
301       DVLOG(1) << "Failed to stop classic discovery";
302       // TODO: Provide a more precise error here.
303       error_callback.Run(UMABluetoothDiscoverySessionOutcome::UNKNOWN);
304       return;
305     }
306   }
307   if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
308     if (IsLowEnergyAvailable())
309       low_energy_discovery_manager_->StopDiscovery();
310   }
312   DVLOG(1) << "Discovery stopped";
313   num_discovery_sessions_--;
314   callback.Run();
317 void BluetoothAdapterMac::SetDiscoveryFilter(
318     scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
319     const base::Closure& callback,
320     const DiscoverySessionErrorCallback& error_callback) {
321   NOTIMPLEMENTED();
322   error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_IMPLEMENTED);
325 bool BluetoothAdapterMac::StartDiscovery(
326     BluetoothDiscoveryFilter* discovery_filter) {
327   // Default to dual discovery if |discovery_filter| is NULL.  IOBluetooth seems
328   // allow starting low energy and classic discovery at once.
329   BluetoothDiscoveryFilter::TransportMask transport =
330       BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
331   if (discovery_filter)
332     transport = discovery_filter->GetTransport();
334   if ((transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) &&
335       !classic_discovery_manager_->IsDiscovering()) {
336     // TODO(krstnmnlsn): If a classic discovery session is already running then
337     // we should update its filter. crbug.com/498056
338     if (!classic_discovery_manager_->StartDiscovery()) {
339       DVLOG(1) << "Failed to add a classic discovery session";
340       return false;
341     }
342   }
343   if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
344     // Begin a low energy discovery session or update it if one is already
345     // running.
346     if (IsLowEnergyAvailable())
347       low_energy_discovery_manager_->StartDiscovery(
348           BluetoothDevice::UUIDList());
349   }
350   return true;
353 void BluetoothAdapterMac::Init() {
354   ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
355   PollAdapter();
358 void BluetoothAdapterMac::InitForTest(
359     scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
360   ui_task_runner_ = ui_task_runner;
363 void BluetoothAdapterMac::PollAdapter() {
364   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
365   // is fixed.
366   tracked_objects::ScopedTracker tracking_profile1(
367       FROM_HERE_WITH_EXPLICIT_FUNCTION(
368           "461181 BluetoothAdapterMac::PollAdapter::Start"));
369   bool was_present = IsPresent();
370   std::string address;
371   bool classic_powered = false;
372   IOBluetoothHostController* controller =
373       [IOBluetoothHostController defaultController];
375   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
376   // is fixed.
377   tracked_objects::ScopedTracker tracking_profile2(
378       FROM_HERE_WITH_EXPLICIT_FUNCTION(
379           "461181 BluetoothAdapterMac::PollAdapter::GetControllerStats"));
380   if (controller != nil) {
381     address = BluetoothDevice::CanonicalizeAddress(
382         base::SysNSStringToUTF8([controller addressAsString]));
383     classic_powered = ([controller powerState] == kBluetoothHCIPowerStateON);
385     // For performance reasons, cache the adapter's name. It's not uncommon for
386     // a call to [controller nameAsString] to take tens of milliseconds. Note
387     // that this caching strategy might result in clients receiving a stale
388     // name. If this is a significant issue, then some more sophisticated
389     // workaround for the performance bottleneck will be needed. For additional
390     // context, see http://crbug.com/461181 and http://crbug.com/467316
391     if (address != address_ || (!address.empty() && name_.empty()))
392       name_ = base::SysNSStringToUTF8([controller nameAsString]);
393   }
395   bool is_present = !address.empty();
396   address_ = address;
398   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
399   // is fixed.
400   tracked_objects::ScopedTracker tracking_profile3(
401       FROM_HERE_WITH_EXPLICIT_FUNCTION(
402           "461181 BluetoothAdapterMac::PollAdapter::AdapterPresentChanged"));
403   if (was_present != is_present) {
404     FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
405                       AdapterPresentChanged(this, is_present));
406   }
408   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
409   // is fixed.
410   tracked_objects::ScopedTracker tracking_profile4(
411       FROM_HERE_WITH_EXPLICIT_FUNCTION(
412           "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged"));
413   if (classic_powered_ != classic_powered) {
414     classic_powered_ = classic_powered;
415     FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
416                       AdapterPoweredChanged(this, classic_powered_));
417   }
419   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
420   // is fixed.
421   tracked_objects::ScopedTracker tracking_profile5(
422       FROM_HERE_WITH_EXPLICIT_FUNCTION(
423           "461181 BluetoothAdapterMac::PollAdapter::RemoveTimedOutDevices"));
424   RemoveTimedOutDevices();
426   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
427   // is fixed.
428   tracked_objects::ScopedTracker tracking_profile6(
429       FROM_HERE_WITH_EXPLICIT_FUNCTION(
430           "461181 BluetoothAdapterMac::PollAdapter::AddPairedDevices"));
431   AddPairedDevices();
433   ui_task_runner_->PostDelayedTask(
434       FROM_HERE,
435       base::Bind(&BluetoothAdapterMac::PollAdapter,
436                  weak_ptr_factory_.GetWeakPtr()),
437       base::TimeDelta::FromMilliseconds(kPollIntervalMs));
440 void BluetoothAdapterMac::ClassicDeviceAdded(IOBluetoothDevice* device) {
441   std::string device_address =
442       BluetoothClassicDeviceMac::GetDeviceAddress(device);
444   // Only notify observers once per device.
445   if (devices_.count(device_address))
446     return;
448   devices_[device_address] = new BluetoothClassicDeviceMac(device);
449   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
450                     observers_,
451                     DeviceAdded(this, devices_[device_address]));
454 void BluetoothAdapterMac::LowEnergyDeviceUpdated(
455     CBPeripheral* peripheral,
456     NSDictionary* advertisement_data,
457     int rssi) {
458   std::string device_address =
459       BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
460   // Get a reference to the actual device pointer held by |devices_| (if
461   // |device_address| has no entry in the map a NULL pointer is created by the
462   // std::map [] operator).
463   BluetoothDevice*& device_reference = devices_[device_address];
464   if (!device_reference) {
465     VLOG(1) << "LowEnergyDeviceUpdated new device";
466     // A new device has been found.
467     device_reference =
468         new BluetoothLowEnergyDeviceMac(peripheral, advertisement_data, rssi);
469     FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
470                       DeviceAdded(this, device_reference));
471     return;
472   }
474   std::string stored_device_id = device_reference->GetIdentifier();
475   std::string updated_device_id =
476       BluetoothLowEnergyDeviceMac::GetPeripheralIdentifier(peripheral);
477   if (stored_device_id != updated_device_id) {
478     VLOG(1) << "LowEnergyDeviceUpdated stored_device_id != updated_device_id: "
479             << std::endl
480             << "  " << stored_device_id << std::endl
481             << "  " << updated_device_id;
482     // Collision, two identifiers map to the same hash address.  With a 48 bit
483     // hash the probability of this occuring with 10,000 devices
484     // simultaneously present is 1e-6 (see
485     // https://en.wikipedia.org/wiki/Birthday_problem#Probability_table).  We
486     // ignore the second device by returning.
487     return;
488   }
490   // A device has an update.
491   VLOG(2) << "LowEnergyDeviceUpdated";
492   static_cast<BluetoothLowEnergyDeviceMac*>(device_reference)
493       ->Update(peripheral, advertisement_data, rssi);
494   FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
495                     DeviceChanged(this, device_reference));
498 // TODO(krstnmnlsn): Implement. crbug.com/511025
499 void BluetoothAdapterMac::LowEnergyCentralManagerUpdatedState() {}
501 void BluetoothAdapterMac::RemoveTimedOutDevices() {
502   // Notify observers if any previously seen devices are no longer available,
503   // i.e. if they are no longer paired, connected, nor recently discovered via
504   // an inquiry.
505   std::set<std::string> removed_devices;
506   for (DevicesMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
507     BluetoothDevice* device = it->second;
508     if (device->IsPaired() || device->IsConnected())
509       continue;
511     NSDate* last_update_time =
512         static_cast<BluetoothDeviceMac*>(device)->GetLastUpdateTime();
513     if (last_update_time &&
514         -[last_update_time timeIntervalSinceNow] < kDiscoveryTimeoutSec)
515       continue;
517     FOR_EACH_OBSERVER(
518         BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
519     delete device;
520     removed_devices.insert(it->first);
521     // The device will be erased from the map in the loop immediately below.
522   }
523   for (const std::string& device_address : removed_devices) {
524     size_t num_removed = devices_.erase(device_address);
525     DCHECK_EQ(num_removed, 1U);
526   }
529 void BluetoothAdapterMac::AddPairedDevices() {
530   // Add any new paired devices.
531   for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
532     ClassicDeviceAdded(device);
533   }
536 }  // namespace device