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