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_low_energy_central_manager_delegate.h"
28 #include "device/bluetooth/bluetooth_socket_mac.h"
29 #include "device/bluetooth/bluetooth_uuid.h"
33 // The frequency with which to poll the adapter for updates.
34 const int kPollIntervalMs = 500;
41 const NSTimeInterval BluetoothAdapterMac::kDiscoveryTimeoutSec =
45 base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
46 const InitCallback& init_callback) {
47 return BluetoothAdapterMac::CreateAdapter();
51 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapter() {
52 BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
54 return adapter->weak_ptr_factory_.GetWeakPtr();
58 base::WeakPtr<BluetoothAdapterMac> BluetoothAdapterMac::CreateAdapterForTest(
61 scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
62 BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
63 adapter->InitForTest(ui_task_runner);
64 adapter->name_ = name;
65 adapter->address_ = address;
66 return adapter->weak_ptr_factory_.GetWeakPtr();
69 BluetoothAdapterMac::BluetoothAdapterMac()
71 classic_powered_(false),
72 num_discovery_sessions_(0),
73 classic_discovery_manager_(
74 BluetoothDiscoveryManagerMac::CreateClassic(this)),
75 weak_ptr_factory_(this) {
76 if (IsLowEnergyAvailable()) {
77 low_energy_discovery_manager_.reset(
78 BluetoothLowEnergyDiscoveryManagerMac::Create(this));
79 low_energy_central_manager_delegate_.reset(
80 [[BluetoothLowEnergyCentralManagerDelegate alloc]
81 initWithDiscoveryManager:low_energy_discovery_manager_.get()
83 Class aClass = NSClassFromString(@"CBCentralManager");
84 low_energy_central_manager_.reset([[aClass alloc]
85 initWithDelegate:low_energy_central_manager_delegate_.get()
86 queue:dispatch_get_main_queue()]);
87 low_energy_discovery_manager_->SetCentralManager(
88 low_energy_central_manager_.get());
90 DCHECK(classic_discovery_manager_.get());
93 BluetoothAdapterMac::~BluetoothAdapterMac() {
96 std::string BluetoothAdapterMac::GetAddress() const {
100 std::string BluetoothAdapterMac::GetName() const {
104 void BluetoothAdapterMac::SetName(const std::string& name,
105 const base::Closure& callback,
106 const ErrorCallback& error_callback) {
110 bool BluetoothAdapterMac::IsInitialized() const {
114 bool BluetoothAdapterMac::IsPresent() const {
115 bool is_present = !address_.empty();
116 if (IsLowEnergyAvailable()) {
117 is_present = is_present || ([low_energy_central_manager_ state] ==
118 CBCentralManagerStatePoweredOn);
123 bool BluetoothAdapterMac::IsPowered() const {
124 bool is_powered = classic_powered_;
125 if (IsLowEnergyAvailable()) {
126 is_powered = is_powered || ([low_energy_central_manager_ state] ==
127 CBCentralManagerStatePoweredOn);
132 void BluetoothAdapterMac::SetPowered(bool powered,
133 const base::Closure& callback,
134 const ErrorCallback& error_callback) {
138 // TODO(krstnmnlsn): If this information is retrievable form IOBluetooth we
139 // should return the discoverable status.
140 bool BluetoothAdapterMac::IsDiscoverable() const {
144 void BluetoothAdapterMac::SetDiscoverable(
146 const base::Closure& callback,
147 const ErrorCallback& error_callback) {
151 bool BluetoothAdapterMac::IsDiscovering() const {
152 bool is_discovering = classic_discovery_manager_->IsDiscovering();
153 if (IsLowEnergyAvailable())
155 is_discovering || low_energy_discovery_manager_->IsDiscovering();
156 return is_discovering;
159 void BluetoothAdapterMac::CreateRfcommService(
160 const BluetoothUUID& uuid,
161 const ServiceOptions& options,
162 const CreateServiceCallback& callback,
163 const CreateServiceErrorCallback& error_callback) {
164 scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
165 socket->ListenUsingRfcomm(
166 this, uuid, options, base::Bind(callback, socket), error_callback);
169 void BluetoothAdapterMac::CreateL2capService(
170 const BluetoothUUID& uuid,
171 const ServiceOptions& options,
172 const CreateServiceCallback& callback,
173 const CreateServiceErrorCallback& error_callback) {
174 scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
175 socket->ListenUsingL2cap(
176 this, uuid, options, base::Bind(callback, socket), error_callback);
179 void BluetoothAdapterMac::RegisterAudioSink(
180 const BluetoothAudioSink::Options& options,
181 const AcquiredCallback& callback,
182 const BluetoothAudioSink::ErrorCallback& error_callback) {
184 error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM);
187 void BluetoothAdapterMac::RegisterAdvertisement(
188 scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
189 const CreateAdvertisementCallback& callback,
190 const CreateAdvertisementErrorCallback& error_callback) {
192 error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
195 void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) {
196 ClassicDeviceAdded(device);
199 void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) {
201 DVLOG(1) << "Discovery stopped unexpectedly";
202 num_discovery_sessions_ = 0;
203 MarkDiscoverySessionsAsInactive();
205 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
207 AdapterDiscoveringChanged(this, false));
210 void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) {
211 // TODO(isherman): Investigate whether this method can be replaced with a call
212 // to +registerForConnectNotifications:selector:.
213 DVLOG(1) << "Adapter registered a new connection from device with address: "
214 << BluetoothClassicDeviceMac::GetDeviceAddress(device);
215 ClassicDeviceAdded(device);
219 bool BluetoothAdapterMac::IsLowEnergyAvailable() {
220 return base::mac::IsOSYosemiteOrLater();
223 void BluetoothAdapterMac::SetCentralManagerForTesting(
224 CBCentralManager* central_manager) {
225 CHECK(BluetoothAdapterMac::IsLowEnergyAvailable());
226 [central_manager performSelector:@selector(setDelegate:)
227 withObject:low_energy_central_manager_delegate_];
228 low_energy_central_manager_.reset(central_manager);
229 low_energy_discovery_manager_->SetCentralManager(
230 low_energy_central_manager_.get());
233 void BluetoothAdapterMac::RemovePairingDelegateInternal(
234 BluetoothDevice::PairingDelegate* pairing_delegate) {
237 void BluetoothAdapterMac::AddDiscoverySession(
238 BluetoothDiscoveryFilter* discovery_filter,
239 const base::Closure& callback,
240 const ErrorCallback& error_callback) {
241 DVLOG(1) << __func__;
242 if (num_discovery_sessions_ > 0) {
243 DCHECK(IsDiscovering());
244 num_discovery_sessions_++;
245 // We are already running a discovery session, notify the system if the
246 // filter has changed.
247 if (!StartDiscovery(discovery_filter)) {
248 error_callback.Run();
255 DCHECK_EQ(0, num_discovery_sessions_);
257 if (!StartDiscovery(discovery_filter)) {
258 error_callback.Run();
262 DVLOG(1) << "Added a discovery session";
263 num_discovery_sessions_++;
264 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
266 AdapterDiscoveringChanged(this, true));
270 void BluetoothAdapterMac::RemoveDiscoverySession(
271 BluetoothDiscoveryFilter* discovery_filter,
272 const base::Closure& callback,
273 const ErrorCallback& error_callback) {
274 DVLOG(1) << __func__;
276 if (num_discovery_sessions_ > 1) {
277 // There are active sessions other than the one currently being removed.
278 DCHECK(IsDiscovering());
279 num_discovery_sessions_--;
284 if (num_discovery_sessions_ == 0) {
285 DVLOG(1) << "No active discovery sessions. Returning error.";
286 error_callback.Run();
290 // Default to dual discovery if |discovery_filter| is NULL.
291 BluetoothDiscoveryFilter::TransportMask transport =
292 BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
293 if (discovery_filter)
294 transport = discovery_filter->GetTransport();
296 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) {
297 if (!classic_discovery_manager_->StopDiscovery()) {
298 DVLOG(1) << "Failed to stop classic discovery";
299 error_callback.Run();
303 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
304 if (IsLowEnergyAvailable())
305 low_energy_discovery_manager_->StopDiscovery();
308 DVLOG(1) << "Discovery stopped";
309 num_discovery_sessions_--;
313 void BluetoothAdapterMac::SetDiscoveryFilter(
314 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
315 const base::Closure& callback,
316 const ErrorCallback& error_callback) {
318 error_callback.Run();
321 bool BluetoothAdapterMac::StartDiscovery(
322 BluetoothDiscoveryFilter* discovery_filter) {
323 // Default to dual discovery if |discovery_filter| is NULL. IOBluetooth seems
324 // allow starting low energy and classic discovery at once.
325 BluetoothDiscoveryFilter::TransportMask transport =
326 BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
327 if (discovery_filter)
328 transport = discovery_filter->GetTransport();
330 if ((transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) &&
331 !classic_discovery_manager_->IsDiscovering()) {
332 // TODO(krstnmnlsn): If a classic discovery session is already running then
333 // we should update its filter. crbug.com/498056
334 if (!classic_discovery_manager_->StartDiscovery()) {
335 DVLOG(1) << "Failed to add a classic discovery session";
339 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
340 // Begin a low energy discovery session or update it if one is already
342 if (IsLowEnergyAvailable())
343 low_energy_discovery_manager_->StartDiscovery(
344 BluetoothDevice::UUIDList());
349 void BluetoothAdapterMac::Init() {
350 ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
354 void BluetoothAdapterMac::InitForTest(
355 scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
356 ui_task_runner_ = ui_task_runner;
359 void BluetoothAdapterMac::PollAdapter() {
360 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
362 tracked_objects::ScopedTracker tracking_profile1(
363 FROM_HERE_WITH_EXPLICIT_FUNCTION(
364 "461181 BluetoothAdapterMac::PollAdapter::Start"));
365 bool was_present = IsPresent();
367 bool classic_powered = false;
368 IOBluetoothHostController* controller =
369 [IOBluetoothHostController defaultController];
371 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
373 tracked_objects::ScopedTracker tracking_profile2(
374 FROM_HERE_WITH_EXPLICIT_FUNCTION(
375 "461181 BluetoothAdapterMac::PollAdapter::GetControllerStats"));
376 if (controller != nil) {
377 address = BluetoothDevice::CanonicalizeAddress(
378 base::SysNSStringToUTF8([controller addressAsString]));
379 classic_powered = ([controller powerState] == kBluetoothHCIPowerStateON);
381 // For performance reasons, cache the adapter's name. It's not uncommon for
382 // a call to [controller nameAsString] to take tens of milliseconds. Note
383 // that this caching strategy might result in clients receiving a stale
384 // name. If this is a significant issue, then some more sophisticated
385 // workaround for the performance bottleneck will be needed. For additional
386 // context, see http://crbug.com/461181 and http://crbug.com/467316
387 if (address != address_ || (!address.empty() && name_.empty()))
388 name_ = base::SysNSStringToUTF8([controller nameAsString]);
391 bool is_present = !address.empty();
394 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
396 tracked_objects::ScopedTracker tracking_profile3(
397 FROM_HERE_WITH_EXPLICIT_FUNCTION(
398 "461181 BluetoothAdapterMac::PollAdapter::AdapterPresentChanged"));
399 if (was_present != is_present) {
400 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
401 AdapterPresentChanged(this, is_present));
404 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
406 tracked_objects::ScopedTracker tracking_profile4(
407 FROM_HERE_WITH_EXPLICIT_FUNCTION(
408 "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged"));
409 if (classic_powered_ != classic_powered) {
410 classic_powered_ = classic_powered;
411 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
412 AdapterPoweredChanged(this, classic_powered_));
415 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
417 tracked_objects::ScopedTracker tracking_profile5(
418 FROM_HERE_WITH_EXPLICIT_FUNCTION(
419 "461181 BluetoothAdapterMac::PollAdapter::RemoveTimedOutDevices"));
420 RemoveTimedOutDevices();
422 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
424 tracked_objects::ScopedTracker tracking_profile6(
425 FROM_HERE_WITH_EXPLICIT_FUNCTION(
426 "461181 BluetoothAdapterMac::PollAdapter::AddPairedDevices"));
429 ui_task_runner_->PostDelayedTask(
431 base::Bind(&BluetoothAdapterMac::PollAdapter,
432 weak_ptr_factory_.GetWeakPtr()),
433 base::TimeDelta::FromMilliseconds(kPollIntervalMs));
436 void BluetoothAdapterMac::ClassicDeviceAdded(IOBluetoothDevice* device) {
437 std::string device_address =
438 BluetoothClassicDeviceMac::GetDeviceAddress(device);
440 // Only notify observers once per device.
441 if (devices_.count(device_address))
444 devices_[device_address] = new BluetoothClassicDeviceMac(device);
445 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
447 DeviceAdded(this, devices_[device_address]));
450 void BluetoothAdapterMac::LowEnergyDeviceUpdated(
451 CBPeripheral* peripheral,
452 NSDictionary* advertisement_data,
454 std::string device_address =
455 BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
456 // Get a reference to the actual device pointer held by |devices_| (if
457 // |device_address| has no entry in the map a NULL pointer is created by the
458 // std::map [] operator).
459 BluetoothDevice*& device_reference = devices_[device_address];
460 if (!device_reference) {
461 // A new device has been found.
463 new BluetoothLowEnergyDeviceMac(peripheral, advertisement_data, rssi);
464 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
465 DeviceAdded(this, device_reference));
469 if (static_cast<BluetoothLowEnergyDeviceMac*>(device_reference)
471 BluetoothLowEnergyDeviceMac::GetPeripheralIdentifier(peripheral)) {
472 // Collision, two identifiers map to the same hash address. With a 48 bit
473 // hash the probability of this occuring with 10,000 devices
474 // simultaneously present is 1e-6 (see
475 // https://en.wikipedia.org/wiki/Birthday_problem#Probability_table). We
476 // ignore the second device by returning.
480 // A device has an update.
481 static_cast<BluetoothLowEnergyDeviceMac*>(device_reference)
482 ->Update(peripheral, advertisement_data, rssi);
483 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
484 DeviceChanged(this, device_reference));
487 // TODO(krstnmnlsn): Implement. crbug.com/511025
488 void BluetoothAdapterMac::LowEnergyCentralManagerUpdatedState() {}
490 void BluetoothAdapterMac::RemoveTimedOutDevices() {
491 // Notify observers if any previously seen devices are no longer available,
492 // i.e. if they are no longer paired, connected, nor recently discovered via
494 std::set<std::string> removed_devices;
495 for (DevicesMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
496 BluetoothDevice* device = it->second;
497 if (device->IsPaired() || device->IsConnected())
500 NSDate* last_update_time =
501 static_cast<BluetoothDeviceMac*>(device)->GetLastUpdateTime();
502 if (last_update_time &&
503 -[last_update_time timeIntervalSinceNow] < kDiscoveryTimeoutSec)
507 BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
509 removed_devices.insert(it->first);
510 // The device will be erased from the map in the loop immediately below.
512 for (const std::string& device_address : removed_devices) {
513 size_t num_removed = devices_.erase(device_address);
514 DCHECK_EQ(num_removed, 1U);
518 void BluetoothAdapterMac::AddPairedDevices() {
519 // Add any new paired devices.
520 for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
521 ClassicDeviceAdded(device);
525 } // namespace device