Remove chromeos==0 blacklist for test_isolation_mode.
[chromium-blink-merge.git] / device / bluetooth / bluetooth_adapter_mac.mm
blob107133e4b179a3e672bf493844ce749fa8b22db9
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/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"
29 namespace {
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
38 }  // namespace
40 namespace device {
42 // static
43 base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
44     const InitCallback& init_callback) {
45   return BluetoothAdapterMac::CreateAdapter();
48 // static
49 base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() {
50   BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
51   adapter->Init();
52   return adapter->weak_ptr_factory_.GetWeakPtr();
55 BluetoothAdapterMac::BluetoothAdapterMac()
56     : BluetoothAdapter(),
57       powered_(false),
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 {
71   return address_;
74 std::string BluetoothAdapterMac::GetName() const {
75   return name_;
78 void BluetoothAdapterMac::SetName(const std::string& name,
79                                   const base::Closure& callback,
80                                   const ErrorCallback& error_callback) {
81   NOTIMPLEMENTED();
84 bool BluetoothAdapterMac::IsInitialized() const {
85   return true;
88 bool BluetoothAdapterMac::IsPresent() const {
89   return !address_.empty();
92 bool BluetoothAdapterMac::IsPowered() const {
93   return powered_;
96 void BluetoothAdapterMac::SetPowered(bool powered,
97                                      const base::Closure& callback,
98                                      const ErrorCallback& error_callback) {
99   NOTIMPLEMENTED();
102 bool BluetoothAdapterMac::IsDiscoverable() const {
103   NOTIMPLEMENTED();
104   return false;
107 void BluetoothAdapterMac::SetDiscoverable(
108     bool discoverable,
109     const base::Closure& callback,
110     const ErrorCallback& error_callback) {
111   NOTIMPLEMENTED();
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) {
143   NOTIMPLEMENTED();
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) {
151   NOTIMPLEMENTED();
152   error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
155 void BluetoothAdapterMac::ClassicDeviceFound(IOBluetoothDevice* device) {
156   ClassicDeviceAdded(device);
159 void BluetoothAdapterMac::ClassicDiscoveryStopped(bool unexpected) {
160   if (unexpected) {
161     DVLOG(1) << "Discovery stopped unexpectedly";
162     num_discovery_sessions_ = 0;
163     MarkDiscoverySessionsAsInactive();
164   }
165   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
166                     observers_,
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();
194       return;
195     }
196     callback.Run();
197     return;
198   }
200   DCHECK_EQ(0, num_discovery_sessions_);
202   if (!StartDiscovery(discovery_filter)) {
203     error_callback.Run();
204     return;
205   }
207   DVLOG(1) << "Added a discovery session";
208   num_discovery_sessions_++;
209   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
210                     observers_,
211                     AdapterDiscoveringChanged(this, true));
212   callback.Run();
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_--;
225     callback.Run();
226     return;
227   }
229   if (num_discovery_sessions_ == 0) {
230     DVLOG(1) << "No active discovery sessions. Returning error.";
231     error_callback.Run();
232     return;
233   }
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();
245       return;
246     }
247   }
248   if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
249     low_energy_discovery_manager_->StopDiscovery();
250   }
252   DVLOG(1) << "Discovery stopped";
253   num_discovery_sessions_--;
254   callback.Run();
257 void BluetoothAdapterMac::SetDiscoveryFilter(
258     scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
259     const base::Closure& callback,
260     const ErrorCallback& error_callback) {
261   NOTIMPLEMENTED();
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";
280       return false;
281     }
282   }
283   if (transport & BluetoothDiscoveryFilter::Transport::TRANSPORT_LE) {
284     // Begin a low energy discovery session or update it if one is already
285     // running.
286     low_energy_discovery_manager_->StartDiscovery(BluetoothDevice::UUIDList());
287   }
288   return true;
291 void BluetoothAdapterMac::Init() {
292   ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
293   PollAdapter();
296 void BluetoothAdapterMac::InitForTest(
297     scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
298   ui_task_runner_ = ui_task_runner;
299   PollAdapter();
302 void BluetoothAdapterMac::PollAdapter() {
303   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
304   // is fixed.
305   tracked_objects::ScopedTracker tracking_profile1(
306       FROM_HERE_WITH_EXPLICIT_FUNCTION(
307           "461181 BluetoothAdapterMac::PollAdapter::Start"));
308   bool was_present = IsPresent();
309   std::string address;
310   bool powered = false;
311   IOBluetoothHostController* controller =
312       [IOBluetoothHostController defaultController];
314   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
315   // is fixed.
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]);
332   }
334   bool is_present = !address.empty();
335   address_ = address;
337   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
338   // is fixed.
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));
345   }
347   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
348   // is fixed.
349   tracked_objects::ScopedTracker tracking_profile4(
350       FROM_HERE_WITH_EXPLICIT_FUNCTION(
351           "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged"));
352   if (powered_ != powered) {
353     powered_ = powered;
354     FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
355                       AdapterPoweredChanged(this, powered_));
356   }
358   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
359   // is fixed.
360   tracked_objects::ScopedTracker tracking_profile5(
361       FROM_HERE_WITH_EXPLICIT_FUNCTION(
362           "461181 BluetoothAdapterMac::PollAdapter::UpdateDevices"));
363   UpdateDevices();
365   ui_task_runner_->PostDelayedTask(
366       FROM_HERE,
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))
378     return;
380   devices_[device_address] = new BluetoothClassicDeviceMac(device);
381   FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
382                     observers_,
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,
391     int rssi) {
394 // TODO(krstnmnlsn): This method assumes all BluetoothDevices in devices_ are
395 // instances of BluetoothDeviceMac.  Add support for low energy devices.
396 // crbug.com/498009
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
400   // an inquiry.
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())
405       continue;
407     NSDate* last_inquiry_update =
408         static_cast<BluetoothClassicDeviceMac*>(device)->GetLastInquiryUpdate();
409     if (last_inquiry_update &&
410         -[last_inquiry_update timeIntervalSinceNow] < kDiscoveryTimeoutSec)
411       continue;
413     FOR_EACH_OBSERVER(
414         BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
415     delete device;
416     removed_devices.insert(it->first);
417     // The device will be erased from the map in the loop immediately below.
418   }
419   for (const std::string& device_address : removed_devices) {
420     size_t num_removed = devices_.erase(device_address);
421     DCHECK_EQ(num_removed, 1U);
422   }
424   // Add any new paired devices.
425   for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
426     ClassicDeviceAdded(device);
427   }
430 }  // namespace device