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.
4
5#include "device/bluetooth/bluetooth_adapter_mac.h"
6
7#import <IOBluetooth/objc/IOBluetoothDevice.h>
8#import <IOBluetooth/objc/IOBluetoothHostController.h>
9
10#include <string>
11
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/sequenced_task_runner.h"
19#include "base/single_thread_task_runner.h"
20#include "base/strings/sys_string_conversions.h"
21#include "base/thread_task_runner_handle.h"
22#include "base/time/time.h"
23#include "device/bluetooth/bluetooth_device_mac.h"
24#include "device/bluetooth/bluetooth_socket_mac.h"
25#include "device/bluetooth/bluetooth_uuid.h"
26
27namespace {
28
29// The frequency with which to poll the adapter for updates.
30const int kPollIntervalMs = 500;
31
32// The length of time that must elapse since the last Inquiry response before a
33// discovered Classic device is considered to be no longer available.
34const NSTimeInterval kDiscoveryTimeoutSec = 3 * 60;  // 3 minutes
35
36}  // namespace
37
38namespace device {
39
40// static
41base::WeakPtr<BluetoothAdapter> BluetoothAdapter::CreateAdapter(
42    const InitCallback& init_callback) {
43  return BluetoothAdapterMac::CreateAdapter();
44}
45
46// static
47base::WeakPtr<BluetoothAdapter> BluetoothAdapterMac::CreateAdapter() {
48  BluetoothAdapterMac* adapter = new BluetoothAdapterMac();
49  adapter->Init();
50  return adapter->weak_ptr_factory_.GetWeakPtr();
51}
52
53BluetoothAdapterMac::BluetoothAdapterMac()
54    : BluetoothAdapter(),
55      powered_(false),
56      num_discovery_sessions_(0),
57      classic_discovery_manager_(
58          BluetoothDiscoveryManagerMac::CreateClassic(this)),
59      weak_ptr_factory_(this) {
60  DCHECK(classic_discovery_manager_.get());
61}
62
63BluetoothAdapterMac::~BluetoothAdapterMac() {
64}
65
66void BluetoothAdapterMac::AddObserver(BluetoothAdapter::Observer* observer) {
67  DCHECK(observer);
68  observers_.AddObserver(observer);
69}
70
71void BluetoothAdapterMac::RemoveObserver(BluetoothAdapter::Observer* observer) {
72  DCHECK(observer);
73  observers_.RemoveObserver(observer);
74}
75
76std::string BluetoothAdapterMac::GetAddress() const {
77  return address_;
78}
79
80std::string BluetoothAdapterMac::GetName() const {
81  return name_;
82}
83
84void BluetoothAdapterMac::SetName(const std::string& name,
85                                  const base::Closure& callback,
86                                  const ErrorCallback& error_callback) {
87  NOTIMPLEMENTED();
88}
89
90bool BluetoothAdapterMac::IsInitialized() const {
91  return true;
92}
93
94bool BluetoothAdapterMac::IsPresent() const {
95  return !address_.empty();
96}
97
98bool BluetoothAdapterMac::IsPowered() const {
99  return powered_;
100}
101
102void BluetoothAdapterMac::SetPowered(bool powered,
103                                     const base::Closure& callback,
104                                     const ErrorCallback& error_callback) {
105  NOTIMPLEMENTED();
106}
107
108bool BluetoothAdapterMac::IsDiscoverable() const {
109  NOTIMPLEMENTED();
110  return false;
111}
112
113void BluetoothAdapterMac::SetDiscoverable(
114    bool discoverable,
115    const base::Closure& callback,
116    const ErrorCallback& error_callback) {
117  NOTIMPLEMENTED();
118}
119
120bool BluetoothAdapterMac::IsDiscovering() const {
121  return classic_discovery_manager_->IsDiscovering();
122}
123
124void BluetoothAdapterMac::CreateRfcommService(
125    const BluetoothUUID& uuid,
126    const ServiceOptions& options,
127    const CreateServiceCallback& callback,
128    const CreateServiceErrorCallback& error_callback) {
129  scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
130  socket->ListenUsingRfcomm(
131      this, uuid, options, base::Bind(callback, socket), error_callback);
132}
133
134void BluetoothAdapterMac::CreateL2capService(
135    const BluetoothUUID& uuid,
136    const ServiceOptions& options,
137    const CreateServiceCallback& callback,
138    const CreateServiceErrorCallback& error_callback) {
139  scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
140  socket->ListenUsingL2cap(
141      this, uuid, options, base::Bind(callback, socket), error_callback);
142}
143
144void BluetoothAdapterMac::DeviceFound(IOBluetoothDevice* device) {
145  DeviceAdded(device);
146}
147
148void BluetoothAdapterMac::DiscoveryStopped(bool unexpected) {
149  if (unexpected) {
150    DVLOG(1) << "Discovery stopped unexpectedly";
151    num_discovery_sessions_ = 0;
152    MarkDiscoverySessionsAsInactive();
153  }
154  FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
155                    observers_,
156                    AdapterDiscoveringChanged(this, false));
157}
158
159void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) {
160  // TODO(isherman): Investigate whether this method can be replaced with a call
161  // to +registerForConnectNotifications:selector:.
162  DVLOG(1) << "Adapter registered a new connection from device with address: "
163           << BluetoothDeviceMac::GetDeviceAddress(device);
164  DeviceAdded(device);
165}
166
167void BluetoothAdapterMac::AddDiscoverySession(
168    const base::Closure& callback,
169    const ErrorCallback& error_callback) {
170  DVLOG(1) << __func__;
171  if (num_discovery_sessions_ > 0) {
172    DCHECK(IsDiscovering());
173    num_discovery_sessions_++;
174    callback.Run();
175    return;
176  }
177
178  DCHECK_EQ(0, num_discovery_sessions_);
179
180  if (!classic_discovery_manager_->StartDiscovery()) {
181    DVLOG(1) << "Failed to add a discovery session";
182    error_callback.Run();
183    return;
184  }
185
186  DVLOG(1) << "Added a discovery session";
187  num_discovery_sessions_++;
188  FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
189                    observers_,
190                    AdapterDiscoveringChanged(this, true));
191  callback.Run();
192}
193
194void BluetoothAdapterMac::RemoveDiscoverySession(
195    const base::Closure& callback,
196    const ErrorCallback& error_callback) {
197  DVLOG(1) << __func__;
198
199  if (num_discovery_sessions_ > 1) {
200    // There are active sessions other than the one currently being removed.
201    DCHECK(IsDiscovering());
202    num_discovery_sessions_--;
203    callback.Run();
204    return;
205  }
206
207  if (num_discovery_sessions_ == 0) {
208    DVLOG(1) << "No active discovery sessions. Returning error.";
209    error_callback.Run();
210    return;
211  }
212
213  if (!classic_discovery_manager_->StopDiscovery()) {
214    DVLOG(1) << "Failed to stop discovery";
215    error_callback.Run();
216    return;
217  }
218
219  DVLOG(1) << "Discovery stopped";
220  num_discovery_sessions_--;
221  callback.Run();
222}
223
224void BluetoothAdapterMac::RemovePairingDelegateInternal(
225    BluetoothDevice::PairingDelegate* pairing_delegate) {
226}
227
228void BluetoothAdapterMac::Init() {
229  ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
230  PollAdapter();
231}
232
233void BluetoothAdapterMac::InitForTest(
234    scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
235  ui_task_runner_ = ui_task_runner;
236  PollAdapter();
237}
238
239void BluetoothAdapterMac::PollAdapter() {
240  bool was_present = IsPresent();
241  std::string name;
242  std::string address;
243  bool powered = false;
244  IOBluetoothHostController* controller =
245      [IOBluetoothHostController defaultController];
246
247  if (controller != nil) {
248    name = base::SysNSStringToUTF8([controller nameAsString]);
249    address = BluetoothDevice::CanonicalizeAddress(
250        base::SysNSStringToUTF8([controller addressAsString]));
251    powered = ([controller powerState] == kBluetoothHCIPowerStateON);
252  }
253
254  bool is_present = !address.empty();
255  name_ = name;
256  address_ = address;
257
258  if (was_present != is_present) {
259    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
260                      AdapterPresentChanged(this, is_present));
261  }
262  if (powered_ != powered) {
263    powered_ = powered;
264    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
265                      AdapterPoweredChanged(this, powered_));
266  }
267
268  UpdateDevices();
269
270  ui_task_runner_->PostDelayedTask(
271      FROM_HERE,
272      base::Bind(&BluetoothAdapterMac::PollAdapter,
273                 weak_ptr_factory_.GetWeakPtr()),
274      base::TimeDelta::FromMilliseconds(kPollIntervalMs));
275}
276
277void BluetoothAdapterMac::DeviceAdded(IOBluetoothDevice* device) {
278  std::string device_address = BluetoothDeviceMac::GetDeviceAddress(device);
279
280  // Only notify observers once per device.
281  if (devices_.count(device_address))
282    return;
283
284  devices_[device_address] = new BluetoothDeviceMac(device);
285  FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
286                    observers_,
287                    DeviceAdded(this, devices_[device_address]));
288}
289
290void BluetoothAdapterMac::UpdateDevices() {
291  // Notify observers if any previously seen devices are no longer available,
292  // i.e. if they are no longer paired, connected, nor recently discovered via
293  // an inquiry.
294  std::set<std::string> removed_devices;
295  for (DevicesMap::iterator it = devices_.begin(); it != devices_.end(); ++it) {
296    BluetoothDevice* device = it->second;
297    if (device->IsPaired() || device->IsConnected())
298      continue;
299
300    NSDate* last_inquiry_update =
301        static_cast<BluetoothDeviceMac*>(device)->GetLastInquiryUpdate();
302    if (last_inquiry_update &&
303        -[last_inquiry_update timeIntervalSinceNow] < kDiscoveryTimeoutSec)
304      continue;
305
306    FOR_EACH_OBSERVER(
307        BluetoothAdapter::Observer, observers_, DeviceRemoved(this, device));
308    delete device;
309    removed_devices.insert(it->first);
310    // The device will be erased from the map in the loop immediately below.
311  }
312  for (const std::string& device_address : removed_devices) {
313    size_t num_removed = devices_.erase(device_address);
314    DCHECK_EQ(num_removed, 1U);
315  }
316
317  // Add any new paired devices.
318  for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) {
319    DeviceAdded(device);
320  }
321}
322
323}  // namespace device
324