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/sdk_forward_declarations.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/profiler/scoped_tracker.h"
19 #include "base/sequenced_task_runner.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "base/thread_task_runner_handle.h"
23 #include "base/time/time.h"
24 #include "device/bluetooth/bluetooth_classic_device_mac.h"
25 #include "device/bluetooth/bluetooth_discovery_session.h"
26 #include "device/bluetooth/bluetooth_socket_mac.h"
27 #include "device/bluetooth/bluetooth_uuid.h"
31 // The frequency with which to poll the adapter for updates.
32 const int kPollIntervalMs = 500;
34 // The length of time that must elapse since the last Inquiry response before a
35 // discovered Classic device is considered to be no longer available.
36 const NSTimeInterval kDiscoveryTimeoutSec = 3 * 60; // 3 minutes
43 base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
44 const InitCallback& init_callback) {
45 return BluetoothAdapterMac::CreateAdapter();
49 base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() {
50 BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
52 return adapter->weak_ptr_factory_.GetWeakPtr();
55 BluetoothAdapterMac::BluetoothAdapterMac()
58 num_discovery_sessions_(0),
59 classic_discovery_manager_(
60 BluetoothDiscoveryManagerMac::CreateClassic(this)),
61 low_energy_discovery_manager_(
62 BluetoothLowEnergyDiscoveryManagerMac::Create(this)),
63 weak_ptr_factory_(this) {
64 DCHECK(classic_discovery_manager_.get());
67 BluetoothAdapterMac::~BluetoothAdapterMac() {
70 std::string BluetoothAdapterMac::GetAddress() const {
74 std::string BluetoothAdapterMac::GetName() const {
78 void BluetoothAdapterMac::SetName(const std::string& name,
79 const base::Closure& callback,
80 const ErrorCallback& error_callback) {
84 bool BluetoothAdapterMac::IsInitialized() const {
88 bool BluetoothAdapterMac::IsPresent() const {
89 return !address_.empty();
92 bool BluetoothAdapterMac::IsPowered() const {
96 void BluetoothAdapterMac::SetPowered(bool powered,
97 const base::Closure& callback,
98 const ErrorCallback& error_callback) {
102 bool BluetoothAdapterMac::IsDiscoverable() const {
107 void BluetoothAdapterMac::SetDiscoverable(
109 const base::Closure& callback,
110 const ErrorCallback& error_callback) {
114 bool BluetoothAdapterMac::IsDiscovering() const {
115 return (classic_discovery_manager_->IsDiscovering() ||
116 low_energy_discovery_manager_->IsDiscovering());
119 void BluetoothAdapterMac::CreateRfcommService(
120 const BluetoothUUID& uuid,
121 const ServiceOptions& options,
122 const CreateServiceCallback& callback,
123 const CreateServiceErrorCallback& error_callback) {
124 scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
125 socket->ListenUsingRfcomm(
126 this, uuid, options, base::Bind(callback, socket), error_callback);
129 void BluetoothAdapterMac::CreateL2capService(
130 const BluetoothUUID& uuid,
131 const ServiceOptions& options,
132 const CreateServiceCallback& callback,
133 const CreateServiceErrorCallback& error_callback) {
134 scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
135 socket->ListenUsingL2cap(
136 this, uuid, options, base::Bind(callback, socket), error_callback);
139 void BluetoothAdapterMac::RegisterAudioSink(
140 const BluetoothAudioSink::Options& options,
141 const AcquiredCallback& callback,
142 const BluetoothAudioSink::ErrorCallback& error_callback) {
144 error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM);
147 void BluetoothAdapterMac::RegisterAdvertisement(
148 scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
149 const CreateAdvertisementCallback& callback,
150 const CreateAdvertisementErrorCallback& error_callback) {
152 error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
155 void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) {
156 ClassicDeviceAdded(device);
159 void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) {
161 DVLOG(1) << "Discovery stopped unexpectedly";
162 num_discovery_sessions_ = 0;
163 MarkDiscoverySessionsAsInactive();
165 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
167 AdapterDiscoveringChanged(this, false));
170 void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) {
171 // TODO(isherman): Investigate whether this method can be replaced with a call
172 // to +registerForConnectNotifications:selector:.
173 DVLOG(1) << "Adapter registered a new connection from device with address: "
174 << BluetoothClassicDeviceMac::GetDeviceAddress(device);
175 ClassicDeviceAdded(device);
178 void BluetoothAdapterMac::RemovePairingDelegateInternal(
179 BluetoothDevice::PairingDelegate* pairing_delegate) {
182 void BluetoothAdapterMac::AddDiscoverySession(
183 BluetoothDiscoveryFilter* discovery_filter,
184 const base::Closure& callback,
185 const ErrorCallback& error_callback) {
186 DVLOG(1) << __func__;
187 if (num_discovery_sessions_ > 0) {
188 DCHECK(IsDiscovering());
189 num_discovery_sessions_++;
190 // We are already running a discovery session, notify the system if the
191 // filter has changed.
192 if (!StartDiscovery(discovery_filter)) {
193 error_callback.Run();
200 DCHECK_EQ(0, num_discovery_sessions_);
202 if (!StartDiscovery(discovery_filter)) {
203 error_callback.Run();
207 DVLOG(1) << "Added a discovery session";
208 num_discovery_sessions_++;
209 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
211 AdapterDiscoveringChanged(this, true));
215 void BluetoothAdapterMac::RemoveDiscoverySession(
216 BluetoothDiscoveryFilter* discovery_filter,
217 const base::Closure& callback,
218 const ErrorCallback& error_callback) {
219 DVLOG(1) << __func__;
221 if (num_discovery_sessions_ > 1) {
222 // There are active sessions other than the one currently being removed.
223 DCHECK(IsDiscovering());
224 num_discovery_sessions_--;
229 if (num_discovery_sessions_ == 0) {
230 DVLOG(1) << "No active discovery sessions. Returning error.";
231 error_callback.Run();
235 // Default to dual discovery if |discovery_filter| is NULL.
236 BluetoothDiscoveryFilter::TransportMask transport =
237 BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
238 if (discovery_filter)
239 transport = discovery_filter->GetTransport();
241 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) {
242 if (!classic_discovery_manager_->StopDiscovery()) {
243 DVLOG(1) << "Failed to stop classic discovery";
244 error_callback.Run();
248 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
249 low_energy_discovery_manager_->StopDiscovery();
252 DVLOG(1) << "Discovery stopped";
253 num_discovery_sessions_--;
257 void BluetoothAdapterMac::SetDiscoveryFilter(
258 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
259 const base::Closure& callback,
260 const ErrorCallback& error_callback) {
262 error_callback.Run();
265 bool BluetoothAdapterMac::StartDiscovery(
266 BluetoothDiscoveryFilter* discovery_filter) {
267 // Default to dual discovery if |discovery_filter| is NULL. IOBluetooth seems
268 // allow starting low energy and classic discovery at once.
269 BluetoothDiscoveryFilter::TransportMask transport =
270 BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
271 if (discovery_filter)
272 transport = discovery_filter->GetTransport();
274 if ((transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC) &&
275 !classic_discovery_manager_->IsDiscovering()) {
276 // TODO(krstnmnlsn): If a classic discovery session is already running then
277 // we should update its filter. crbug.com/498056
278 if (!classic_discovery_manager_->StartDiscovery()) {
279 DVLOG(1) << "Failed to add a classic discovery session";
283 if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
284 // Begin a low energy discovery session or update it if one is already
286 low_energy_discovery_manager_->StartDiscovery(BluetoothDevice::UUIDList());
291 void BluetoothAdapterMac::Init() {
292 ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
296 void BluetoothAdapterMac::InitForTest(
297 scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
298 ui_task_runner_ = ui_task_runner;
302 void BluetoothAdapterMac::PollAdapter() {
303 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
305 tracked_objects::ScopedTracker tracking_profile1(
306 FROM_HERE_WITH_EXPLICIT_FUNCTION(
307 "461181 BluetoothAdapterMac::PollAdapter::Start"));
308 bool was_present = IsPresent();
310 bool powered = false;
311 IOBluetoothHostController* controller =
312 [IOBluetoothHostController defaultController];
314 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
316 tracked_objects::ScopedTracker tracking_profile2(
317 FROM_HERE_WITH_EXPLICIT_FUNCTION(
318 "461181 BluetoothAdapterMac::PollAdapter::GetControllerStats"));
319 if (controller != nil) {
320 address = BluetoothDevice::CanonicalizeAddress(
321 base::SysNSStringToUTF8([controller addressAsString]));
322 powered = ([controller powerState] == kBluetoothHCIPowerStateON);
324 // For performance reasons, cache the adapter's name. It's not uncommon for
325 // a call to [controller nameAsString] to take tens of milliseconds. Note
326 // that this caching strategy might result in clients receiving a stale
327 // name. If this is a significant issue, then some more sophisticated
328 // workaround for the performance bottleneck will be needed. For additional
329 // context, see http://crbug.com/461181 and http://crbug.com/467316
330 if (address != address_ || (!address.empty() && name_.empty()))
331 name_ = base::SysNSStringToUTF8([controller nameAsString]);
334 bool is_present = !address.empty();
337 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
339 tracked_objects::ScopedTracker tracking_profile3(
340 FROM_HERE_WITH_EXPLICIT_FUNCTION(
341 "461181 BluetoothAdapterMac::PollAdapter::AdapterPresentChanged"));
342 if (was_present != is_present) {
343 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
344 AdapterPresentChanged(this, is_present));
347 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
349 tracked_objects::ScopedTracker tracking_profile4(
350 FROM_HERE_WITH_EXPLICIT_FUNCTION(
351 "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged"));
352 if (powered_ != powered) {
354 FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
355 AdapterPoweredChanged(this, powered_));
358 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
360 tracked_objects::ScopedTracker tracking_profile5(
361 FROM_HERE_WITH_EXPLICIT_FUNCTION(
362 "461181 BluetoothAdapterMac::PollAdapter::UpdateDevices"));
365 ui_task_runner_->PostDelayedTask(
367 base::Bind(&BluetoothAdapterMac::PollAdapter,
368 weak_ptr_factory_.GetWeakPtr()),
369 base::TimeDelta::FromMilliseconds(kPollIntervalMs));
372 void BluetoothAdapterMac::ClassicDeviceAdded(IOBluetoothDevice* device) {
373 std::string device_address =
374 BluetoothClassicDeviceMac::GetDeviceAddress(device);
376 // Only notify observers once per device.
377 if (devices_.count(device_address))
380 devices_[device_address] = new BluetoothClassicDeviceMac(device);
381 FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
383 DeviceAdded(this, devices_[device_address]));
386 // TODO(krstnmnlsn): This method to be implemented as soon as UpdateDevices can
387 // handle instances of LowEnergyBluetoothDevice in |devices_|. crbug.com/498009
388 void BluetoothAdapterMac::LowEnergyDeviceUpdated(
389 CBPeripheral* peripheral,
390 NSDictionary* advertisementData,
394 // TODO(krstnmnlsn): This method assumes all BluetoothDevices in devices_ are
395 // instances of BluetoothDeviceMac. Add support for low energy devices.
397 void BluetoothAdapterMac::UpdateDevices() {
398 // Notify observers if any previously seen devices are no longer available,
399 // i.e. if they are no longer paired, connected, nor recently discovered via
401 std::set<std::string> removed_devices;
402 for (DevicesMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
403 BluetoothDevice* device = it->second;
404 if (device->IsPaired() || device->IsConnected())
407 NSDate* last_inquiry_update =
408 static_cast<BluetoothClassicDeviceMac*>(device)->GetLastInquiryUpdate();
409 if (last_inquiry_update &&
410 -[last_inquiry_update timeIntervalSinceNow] < kDiscoveryTimeoutSec)
414 BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
416 removed_devices.insert(it->first);
417 // The device will be erased from the map in the loop immediately below.
419 for (const std::string& device_address : removed_devices) {
420 size_t num_removed = devices_.erase(device_address);
421 DCHECK_EQ(num_removed, 1U);
424 // Add any new paired devices.
425 for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
426 ClassicDeviceAdded(device);
430 } // namespace device