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_chromeos.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "base/metrics/histogram.h"
12#include "base/sys_info.h"
13#include "chromeos/dbus/bluetooth_adapter_client.h"
14#include "chromeos/dbus/bluetooth_device_client.h"
15#include "chromeos/dbus/bluetooth_input_client.h"
16#include "chromeos/dbus/dbus_thread_manager.h"
17#include "device/bluetooth/bluetooth_device.h"
18#include "device/bluetooth/bluetooth_device_chromeos.h"
19
20using device::BluetoothAdapter;
21using device::BluetoothDevice;
22
23namespace chromeos {
24
25BluetoothAdapterChromeOS::BluetoothAdapterChromeOS()
26    : weak_ptr_factory_(this) {
27  DBusThreadManager::Get()->GetBluetoothAdapterClient()->AddObserver(this);
28  DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this);
29  DBusThreadManager::Get()->GetBluetoothInputClient()->AddObserver(this);
30
31  std::vector<dbus::ObjectPath> object_paths =
32      DBusThreadManager::Get()->GetBluetoothAdapterClient()->GetAdapters();
33
34  if (!object_paths.empty()) {
35    VLOG(1) << object_paths.size() << " Bluetooth adapter(s) available.";
36    SetAdapter(object_paths[0]);
37  }
38}
39
40BluetoothAdapterChromeOS::~BluetoothAdapterChromeOS() {
41  DBusThreadManager::Get()->GetBluetoothAdapterClient()->RemoveObserver(this);
42  DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this);
43  DBusThreadManager::Get()->GetBluetoothInputClient()->RemoveObserver(this);
44}
45
46void BluetoothAdapterChromeOS::AddObserver(
47    BluetoothAdapter::Observer* observer) {
48  DCHECK(observer);
49  observers_.AddObserver(observer);
50}
51
52void BluetoothAdapterChromeOS::RemoveObserver(
53    BluetoothAdapter::Observer* observer) {
54  DCHECK(observer);
55  observers_.RemoveObserver(observer);
56}
57
58std::string BluetoothAdapterChromeOS::GetAddress() const {
59  if (!IsPresent())
60    return std::string();
61
62  BluetoothAdapterClient::Properties* properties =
63      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
64          GetProperties(object_path_);
65  DCHECK(properties);
66
67  return properties->address.value();
68}
69
70std::string BluetoothAdapterChromeOS::GetName() const {
71  if (!IsPresent())
72    return std::string();
73
74  BluetoothAdapterClient::Properties* properties =
75      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
76          GetProperties(object_path_);
77  DCHECK(properties);
78
79  return properties->alias.value();
80}
81
82bool BluetoothAdapterChromeOS::IsInitialized() const {
83  return true;
84}
85
86bool BluetoothAdapterChromeOS::IsPresent() const {
87  return !object_path_.value().empty();
88}
89
90bool BluetoothAdapterChromeOS::IsPowered() const {
91  if (!IsPresent())
92    return false;
93
94  BluetoothAdapterClient::Properties* properties =
95      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
96          GetProperties(object_path_);
97
98  return properties->powered.value();
99}
100
101void BluetoothAdapterChromeOS::SetPowered(
102    bool powered,
103    const base::Closure& callback,
104    const ErrorCallback& error_callback) {
105  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
106      GetProperties(object_path_)->powered.Set(
107          powered,
108          base::Bind(&BluetoothAdapterChromeOS::OnSetPowered,
109                     weak_ptr_factory_.GetWeakPtr(),
110                     callback,
111                     error_callback));
112}
113
114bool BluetoothAdapterChromeOS::IsDiscovering() const {
115  if (!IsPresent())
116    return false;
117
118  BluetoothAdapterClient::Properties* properties =
119      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
120          GetProperties(object_path_);
121
122  return properties->discovering.value();
123}
124
125void BluetoothAdapterChromeOS::StartDiscovering(
126    const base::Closure& callback,
127    const ErrorCallback& error_callback) {
128  // BlueZ counts discovery sessions, and permits multiple sessions for a
129  // single connection, so issue a StartDiscovery() call for every use
130  // within Chromium for the right behavior.
131  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
132      StartDiscovery(
133          object_path_,
134          base::Bind(&BluetoothAdapterChromeOS::OnStartDiscovery,
135                     weak_ptr_factory_.GetWeakPtr(),
136                     callback),
137          base::Bind(&BluetoothAdapterChromeOS::OnStartDiscoveryError,
138                     weak_ptr_factory_.GetWeakPtr(),
139                     error_callback));
140}
141
142void BluetoothAdapterChromeOS::StopDiscovering(
143    const base::Closure& callback,
144    const ErrorCallback& error_callback) {
145  // Inform BlueZ to stop one of our open discovery sessions.
146  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
147      StopDiscovery(
148          object_path_,
149          base::Bind(&BluetoothAdapterChromeOS::OnStopDiscovery,
150                     weak_ptr_factory_.GetWeakPtr(),
151                     callback),
152          base::Bind(&BluetoothAdapterChromeOS::OnStopDiscoveryError,
153                     weak_ptr_factory_.GetWeakPtr(),
154                     error_callback));
155}
156
157void BluetoothAdapterChromeOS::ReadLocalOutOfBandPairingData(
158    const BluetoothAdapter::BluetoothOutOfBandPairingDataCallback& callback,
159    const ErrorCallback& error_callback) {
160  error_callback.Run();
161}
162
163void BluetoothAdapterChromeOS::AdapterAdded(
164    const dbus::ObjectPath& object_path) {
165  // Set the adapter to the newly added adapter only if no adapter is present.
166  if (!IsPresent())
167    SetAdapter(object_path);
168}
169
170void BluetoothAdapterChromeOS::AdapterRemoved(
171    const dbus::ObjectPath& object_path) {
172  if (object_path == object_path_)
173    RemoveAdapter();
174}
175
176void BluetoothAdapterChromeOS::AdapterPropertyChanged(
177    const dbus::ObjectPath& object_path,
178    const std::string& property_name) {
179  if (object_path != object_path_)
180    return;
181
182  BluetoothAdapterClient::Properties* properties =
183      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
184          GetProperties(object_path_);
185
186  if (property_name == properties->powered.name())
187    PoweredChanged(properties->powered.value());
188  else if (property_name == properties->discovering.name())
189    DiscoveringChanged(properties->discovering.value());
190}
191
192void BluetoothAdapterChromeOS::DeviceAdded(
193  const dbus::ObjectPath& object_path) {
194  BluetoothDeviceClient::Properties* properties =
195      DBusThreadManager::Get()->GetBluetoothDeviceClient()->
196          GetProperties(object_path);
197  if (properties->adapter.value() != object_path_)
198    return;
199
200  BluetoothDeviceChromeOS* device_chromeos =
201      new BluetoothDeviceChromeOS(this, object_path);
202  DCHECK(devices_.find(device_chromeos->GetAddress()) == devices_.end());
203
204  devices_[device_chromeos->GetAddress()] = device_chromeos;
205
206  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
207                    DeviceAdded(this, device_chromeos));
208}
209
210void BluetoothAdapterChromeOS::DeviceRemoved(
211    const dbus::ObjectPath& object_path) {
212  for (DevicesMap::iterator iter = devices_.begin();
213       iter != devices_.end(); ++iter) {
214    BluetoothDeviceChromeOS* device_chromeos =
215        static_cast<BluetoothDeviceChromeOS*>(iter->second);
216    if (device_chromeos->object_path() == object_path) {
217      devices_.erase(iter);
218
219      FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
220                        DeviceRemoved(this, device_chromeos));
221      delete device_chromeos;
222      return;
223    }
224  }
225}
226
227void BluetoothAdapterChromeOS::DevicePropertyChanged(
228    const dbus::ObjectPath& object_path,
229    const std::string& property_name) {
230  BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path);
231  if (!device_chromeos)
232    return;
233
234  BluetoothDeviceClient::Properties* properties =
235      DBusThreadManager::Get()->GetBluetoothDeviceClient()->
236          GetProperties(object_path);
237
238  if (property_name == properties->bluetooth_class.name() ||
239      property_name == properties->address.name() ||
240      property_name == properties->alias.name() ||
241      property_name == properties->paired.name() ||
242      property_name == properties->trusted.name() ||
243      property_name == properties->connected.name() ||
244      property_name == properties->uuids.name())
245    NotifyDeviceChanged(device_chromeos);
246
247  // UMA connection counting
248  if (property_name == properties->connected.name()) {
249    int count = 0;
250
251    for (DevicesMap::iterator iter = devices_.begin();
252         iter != devices_.end(); ++iter) {
253      if (iter->second->IsPaired() && iter->second->IsConnected())
254        ++count;
255    }
256
257    UMA_HISTOGRAM_COUNTS_100("Bluetooth.ConnectedDeviceCount", count);
258  }
259}
260
261void BluetoothAdapterChromeOS::InputPropertyChanged(
262    const dbus::ObjectPath& object_path,
263    const std::string& property_name) {
264  BluetoothDeviceChromeOS* device_chromeos = GetDeviceWithPath(object_path);
265  if (!device_chromeos)
266    return;
267
268  BluetoothInputClient::Properties* properties =
269      DBusThreadManager::Get()->GetBluetoothInputClient()->
270          GetProperties(object_path);
271
272  // Properties structure can be removed, which triggers a change in the
273  // BluetoothDevice::IsConnectable() property, as does a change in the
274  // actual reconnect_mode property.
275  if (!properties ||
276      property_name == properties->reconnect_mode.name())
277    NotifyDeviceChanged(device_chromeos);
278}
279
280BluetoothDeviceChromeOS*
281BluetoothAdapterChromeOS::GetDeviceWithPath(
282    const dbus::ObjectPath& object_path) {
283  for (DevicesMap::iterator iter = devices_.begin();
284       iter != devices_.end(); ++iter) {
285    BluetoothDeviceChromeOS* device_chromeos =
286        static_cast<BluetoothDeviceChromeOS*>(iter->second);
287    if (device_chromeos->object_path() == object_path)
288      return device_chromeos;
289  }
290
291  return NULL;
292}
293
294void BluetoothAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) {
295  DCHECK(!IsPresent());
296  object_path_ = object_path;
297
298  VLOG(1) << object_path_.value() << ": using adapter.";
299
300  SetAdapterName();
301
302  BluetoothAdapterClient::Properties* properties =
303      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
304          GetProperties(object_path_);
305
306  PresentChanged(true);
307
308  if (properties->powered.value())
309    PoweredChanged(true);
310  if (properties->discovering.value())
311    DiscoveringChanged(true);
312
313  std::vector<dbus::ObjectPath> device_paths =
314      DBusThreadManager::Get()->GetBluetoothDeviceClient()->
315          GetDevicesForAdapter(object_path_);
316
317  for (std::vector<dbus::ObjectPath>::iterator iter = device_paths.begin();
318       iter != device_paths.end(); ++iter) {
319    BluetoothDeviceChromeOS* device_chromeos =
320        new BluetoothDeviceChromeOS(this, *iter);
321
322    devices_[device_chromeos->GetAddress()] = device_chromeos;
323
324    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
325                      DeviceAdded(this, device_chromeos));
326  }
327}
328
329void BluetoothAdapterChromeOS::SetAdapterName() {
330  std::string board = base::SysInfo::GetLsbReleaseBoard();
331  std::string alias;
332  if (board.substr(0, 6) == "stumpy") {
333    alias = "Chromebox";
334  } else if (board.substr(0, 4) == "link") {
335    alias = "Chromebook Pixel";
336  } else {
337    alias = "Chromebook";
338  }
339
340  DBusThreadManager::Get()->GetBluetoothAdapterClient()->
341      GetProperties(object_path_)->alias.Set(
342          alias,
343          base::Bind(&BluetoothAdapterChromeOS::OnSetAlias,
344                     weak_ptr_factory_.GetWeakPtr()));
345}
346
347void BluetoothAdapterChromeOS::OnSetAlias(bool success) {
348  LOG_IF(WARNING, !success) << object_path_.value()
349                            << ": Failed to set adapter alias";
350}
351
352void BluetoothAdapterChromeOS::RemoveAdapter() {
353  DCHECK(IsPresent());
354  VLOG(1) << object_path_.value() << ": adapter removed.";
355
356  BluetoothAdapterClient::Properties* properties =
357      DBusThreadManager::Get()->GetBluetoothAdapterClient()->
358          GetProperties(object_path_);
359
360  object_path_ = dbus::ObjectPath("");
361
362  if (properties->powered.value())
363    PoweredChanged(false);
364  if (properties->discovering.value())
365    DiscoveringChanged(false);
366
367  // Copy the devices list here and clear the original so that when we
368  // send DeviceRemoved(), GetDevices() returns no devices.
369  DevicesMap devices = devices_;
370  devices_.clear();
371
372  for (DevicesMap::iterator iter = devices.begin();
373       iter != devices.end(); ++iter) {
374    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
375                      DeviceRemoved(this, iter->second));
376    delete iter->second;
377  }
378
379  PresentChanged(false);
380}
381
382void BluetoothAdapterChromeOS::PoweredChanged(bool powered) {
383  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
384                    AdapterPoweredChanged(this, powered));
385}
386
387void BluetoothAdapterChromeOS::DiscoveringChanged(
388    bool discovering) {
389  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
390                    AdapterDiscoveringChanged(this, discovering));
391}
392
393void BluetoothAdapterChromeOS::PresentChanged(bool present) {
394  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
395                    AdapterPresentChanged(this, present));
396}
397
398void BluetoothAdapterChromeOS::NotifyDeviceChanged(
399    BluetoothDeviceChromeOS* device) {
400  DCHECK(device->adapter_ == this);
401
402  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
403                    DeviceChanged(this, device));
404}
405
406void BluetoothAdapterChromeOS::OnSetPowered(const base::Closure& callback,
407                                            const ErrorCallback& error_callback,
408                                            bool success) {
409  if (success)
410    callback.Run();
411  else
412    error_callback.Run();
413}
414
415void BluetoothAdapterChromeOS::OnStartDiscovery(const base::Closure& callback) {
416  callback.Run();
417}
418
419void BluetoothAdapterChromeOS::OnStartDiscoveryError(
420    const ErrorCallback& error_callback,
421    const std::string& error_name,
422    const std::string& error_message) {
423  LOG(WARNING) << object_path_.value() << ": Failed to start discovery: "
424               << error_name << ": " << error_message;
425  error_callback.Run();
426}
427
428void BluetoothAdapterChromeOS::OnStopDiscovery(const base::Closure& callback) {
429  callback.Run();
430}
431
432void BluetoothAdapterChromeOS::OnStopDiscoveryError(
433    const ErrorCallback& error_callback,
434    const std::string& error_name,
435    const std::string& error_message) {
436  LOG(WARNING) << object_path_.value() << ": Failed to stop discovery: "
437               << error_name << ": " << error_message;
438  error_callback.Run();
439}
440
441}  // namespace chromeos
442