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>
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"
34 // The frequency with which to poll the adapter for updates.
35 const int kPollIntervalMs = 500;
42 const NSTimeInterval BluetoothAdapterMac::kDiscoveryTimeoutSec =
46 base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
47 const InitCallback& init_callback) {
48 return BluetoothAdapterMac::CreateAdapter();
52 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapter() {
53 BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
55 return adapter->weak_ptr_factory_.GetWeakPtr();
59 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapterForTest(
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()
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()
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());
91 DCHECK(classic_discovery_manager_.get());
94 BluetoothAdapterMac::~BluetoothAdapterMac() {
97 std::string BluetoothAdapterMac::GetAddress() const {
101 std::string BluetoothAdapterMac::GetName() const {
105 void BluetoothAdapterMac::SetName(const std::string& name,
106 const base::Closure& callback,
107 const ErrorCallback& error_callback) {
111 bool BluetoothAdapterMac::IsInitialized() const {
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);
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);
133 void BluetoothAdapterMac::SetPowered(bool powered,
134 const base::Closure& callback,
135 const ErrorCallback& error_callback) {
139 // TODO(krstnmnlsn): If this information is retrievable form IOBluetooth we
140 // should return the discoverable status.
141 bool BluetoothAdapterMac::IsDiscoverable() const {
145 void BluetoothAdapterMac::SetDiscoverable(
147 const base::Closure& callback,
148 const ErrorCallback& error_callback) {
152 bool BluetoothAdapterMac::IsDiscovering() const {
153 bool is_discovering = classic_discovery_manager_->IsDiscovering();
154 if (IsLowEnergyAvailable())
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) {
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) {
193 error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
196 void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) {
197 ClassicDeviceAdded(device);
200 void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) {
202 DVLOG(1) << "Discovery stopped unexpectedly";
203 num_discovery_sessions_ = 0;
204 MarkDiscoverySessionsAsInactive();
206 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
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);
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);
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);
265 DVLOG(1) << "Added a discovery session";
266 num_discovery_sessions_++;
267 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
269 AdapterDiscoveringChanged(this, true));
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_--;
287 if (num_discovery_sessions_ == 0) {
288 DVLOG(1) << "No active discovery sessions. Returning error.";
289 error_callback.Run(UMABluetoothDiscoverySessionOutcome::NOT_ACTIVE);
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);
307 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
308 if (IsLowEnergyAvailable())
309 low_energy_discovery_manager_->StopDiscovery();
312 DVLOG(1) << "Discovery stopped";
313 num_discovery_sessions_--;
317 void BluetoothAdapterMac::SetDiscoveryFilter(
318 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
319 const base::Closure& callback,
320 const DiscoverySessionErrorCallback& error_callback) {
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";
343 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
344 // Begin a low energy discovery session or update it if one is already
346 if (IsLowEnergyAvailable())
347 low_energy_discovery_manager_->StartDiscovery(
348 BluetoothDevice::UUIDList());
353 void BluetoothAdapterMac::Init() {
354 ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
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
366 tracked_objects::ScopedTracker tracking_profile1(
367 FROM_HERE_WITH_EXPLICIT_FUNCTION(
368 "461181 BluetoothAdapterMac::PollAdapter::Start"));
369 bool was_present = IsPresent();
371 bool classic_powered = false;
372 IOBluetoothHostController* controller =
373 [IOBluetoothHostController defaultController];
375 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
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]);
395 bool is_present = !address.empty();
398 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
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));
408 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
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_));
419 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
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
428 tracked_objects::ScopedTracker tracking_profile6(
429 FROM_HERE_WITH_EXPLICIT_FUNCTION(
430 "461181 BluetoothAdapterMac::PollAdapter::AddPairedDevices"));
433 ui_task_runner_->PostDelayedTask(
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))
448 devices_[device_address] = new BluetoothClassicDeviceMac(device);
449 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
451 DeviceAdded(this, devices_[device_address]));
454 void BluetoothAdapterMac::LowEnergyDeviceUpdated(
455 CBPeripheral* peripheral,
456 NSDictionary* advertisement_data,
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.
468 new BluetoothLowEnergyDeviceMac(peripheral, advertisement_data, rssi);
469 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
470 DeviceAdded(this, device_reference));
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: "
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.
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
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())
511 NSDate* last_update_time =
512 static_cast<BluetoothDeviceMac*>(device)->GetLastUpdateTime();
513 if (last_update_time &&
514 -[last_update_time timeIntervalSinceNow] < kDiscoveryTimeoutSec)
518 BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
520 removed_devices.insert(it->first);
521 // The device will be erased from the map in the loop immediately below.
523 for (const std::string& device_address : removed_devices) {
524 size_t num_removed = devices_.erase(device_address);
525 DCHECK_EQ(num_removed, 1U);
529 void BluetoothAdapterMac::AddPairedDevices() {
530 // Add any new paired devices.
531 for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
532 ClassicDeviceAdded(device);
536 } // namespace device