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_device_chromeos.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "chromeos/dbus/bluetooth_adapter_client.h"
14 #include "chromeos/dbus/bluetooth_device_client.h"
15 #include "chromeos/dbus/bluetooth_gatt_service_client.h"
16 #include "chromeos/dbus/bluetooth_input_client.h"
17 #include "chromeos/dbus/dbus_thread_manager.h"
19 #include "device/bluetooth/bluetooth_adapter_chromeos.h"
20 #include "device/bluetooth/bluetooth_pairing_chromeos.h"
21 #include "device/bluetooth/bluetooth_profile_chromeos.h"
22 #include "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h"
23 #include "device/bluetooth/bluetooth_socket.h"
24 #include "third_party/cros_system_api/dbus/service_constants.h"
26 using device::BluetoothDevice
;
30 // Histogram enumerations for pairing results.
31 enum UMAPairingResult
{
32 UMA_PAIRING_RESULT_SUCCESS
,
33 UMA_PAIRING_RESULT_INPROGRESS
,
34 UMA_PAIRING_RESULT_FAILED
,
35 UMA_PAIRING_RESULT_AUTH_FAILED
,
36 UMA_PAIRING_RESULT_AUTH_CANCELED
,
37 UMA_PAIRING_RESULT_AUTH_REJECTED
,
38 UMA_PAIRING_RESULT_AUTH_TIMEOUT
,
39 UMA_PAIRING_RESULT_UNSUPPORTED_DEVICE
,
40 UMA_PAIRING_RESULT_UNKNOWN_ERROR
,
41 // NOTE: Add new pairing results immediately above this line. Make sure to
42 // update the enum list in tools/histogram/histograms.xml accordinly.
43 UMA_PAIRING_RESULT_COUNT
46 void ParseModalias(const dbus::ObjectPath
& object_path
,
47 BluetoothDevice::VendorIDSource
* vendor_id_source
,
51 chromeos::BluetoothDeviceClient::Properties
* properties
=
52 chromeos::DBusThreadManager::Get()->GetBluetoothDeviceClient()->
53 GetProperties(object_path
);
56 std::string modalias
= properties
->modalias
.value();
57 BluetoothDevice::VendorIDSource source_value
;
58 int vendor_value
, product_value
, device_value
;
60 if (sscanf(modalias
.c_str(), "bluetooth:v%04xp%04xd%04x",
61 &vendor_value
, &product_value
, &device_value
) == 3) {
62 source_value
= BluetoothDevice::VENDOR_ID_BLUETOOTH
;
63 } else if (sscanf(modalias
.c_str(), "usb:v%04xp%04xd%04x",
64 &vendor_value
, &product_value
, &device_value
) == 3) {
65 source_value
= BluetoothDevice::VENDOR_ID_USB
;
70 if (vendor_id_source
!= NULL
)
71 *vendor_id_source
= source_value
;
72 if (vendor_id
!= NULL
)
73 *vendor_id
= vendor_value
;
74 if (product_id
!= NULL
)
75 *product_id
= product_value
;
76 if (device_id
!= NULL
)
77 *device_id
= device_value
;
80 void RecordPairingResult(BluetoothDevice::ConnectErrorCode error_code
) {
81 UMAPairingResult pairing_result
;
83 case BluetoothDevice::ERROR_INPROGRESS
:
84 pairing_result
= UMA_PAIRING_RESULT_INPROGRESS
;
86 case BluetoothDevice::ERROR_FAILED
:
87 pairing_result
= UMA_PAIRING_RESULT_FAILED
;
89 case BluetoothDevice::ERROR_AUTH_FAILED
:
90 pairing_result
= UMA_PAIRING_RESULT_AUTH_FAILED
;
92 case BluetoothDevice::ERROR_AUTH_CANCELED
:
93 pairing_result
= UMA_PAIRING_RESULT_AUTH_CANCELED
;
95 case BluetoothDevice::ERROR_AUTH_REJECTED
:
96 pairing_result
= UMA_PAIRING_RESULT_AUTH_REJECTED
;
98 case BluetoothDevice::ERROR_AUTH_TIMEOUT
:
99 pairing_result
= UMA_PAIRING_RESULT_AUTH_TIMEOUT
;
101 case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE
:
102 pairing_result
= UMA_PAIRING_RESULT_UNSUPPORTED_DEVICE
;
105 pairing_result
= UMA_PAIRING_RESULT_UNKNOWN_ERROR
;
108 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingResult",
110 UMA_PAIRING_RESULT_COUNT
);
117 BluetoothDeviceChromeOS::BluetoothDeviceChromeOS(
118 BluetoothAdapterChromeOS
* adapter
,
119 const dbus::ObjectPath
& object_path
)
121 object_path_(object_path
),
122 num_connecting_calls_(0),
123 weak_ptr_factory_(this) {
124 DBusThreadManager::Get()->GetBluetoothGattServiceClient()->AddObserver(this);
126 // Add all known GATT services.
127 const std::vector
<dbus::ObjectPath
> gatt_services
=
128 DBusThreadManager::Get()->GetBluetoothGattServiceClient()->GetServices();
129 for (std::vector
<dbus::ObjectPath
>::const_iterator it
= gatt_services
.begin();
130 it
!= gatt_services
.end(); ++it
) {
131 GattServiceAdded(*it
);
135 BluetoothDeviceChromeOS::~BluetoothDeviceChromeOS() {
136 DBusThreadManager::Get()->GetBluetoothGattServiceClient()->
137 RemoveObserver(this);
139 // Copy the GATT services list here and clear the original so that when we
140 // send GattServiceRemoved(), GetGattServices() returns no services.
141 GattServiceMap gatt_services
= gatt_services_
;
142 gatt_services_
.clear();
143 for (GattServiceMap::iterator iter
= gatt_services
.begin();
144 iter
!= gatt_services
.end(); ++iter
) {
145 FOR_EACH_OBSERVER(BluetoothDevice::Observer
, observers_
,
146 GattServiceRemoved(this, iter
->second
));
151 void BluetoothDeviceChromeOS::AddObserver(
152 device::BluetoothDevice::Observer
* observer
) {
154 observers_
.AddObserver(observer
);
157 void BluetoothDeviceChromeOS::RemoveObserver(
158 device::BluetoothDevice::Observer
* observer
) {
160 observers_
.RemoveObserver(observer
);
163 uint32
BluetoothDeviceChromeOS::GetBluetoothClass() const {
164 BluetoothDeviceClient::Properties
* properties
=
165 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
166 GetProperties(object_path_
);
169 return properties
->bluetooth_class
.value();
172 std::string
BluetoothDeviceChromeOS::GetDeviceName() const {
173 BluetoothDeviceClient::Properties
* properties
=
174 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
175 GetProperties(object_path_
);
178 return properties
->alias
.value();
181 std::string
BluetoothDeviceChromeOS::GetAddress() const {
182 BluetoothDeviceClient::Properties
* properties
=
183 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
184 GetProperties(object_path_
);
187 return properties
->address
.value();
190 BluetoothDevice::VendorIDSource
191 BluetoothDeviceChromeOS::GetVendorIDSource() const {
192 VendorIDSource vendor_id_source
= VENDOR_ID_UNKNOWN
;
193 ParseModalias(object_path_
, &vendor_id_source
, NULL
, NULL
, NULL
);
194 return vendor_id_source
;
197 uint16
BluetoothDeviceChromeOS::GetVendorID() const {
198 uint16 vendor_id
= 0;
199 ParseModalias(object_path_
, NULL
, &vendor_id
, NULL
, NULL
);
203 uint16
BluetoothDeviceChromeOS::GetProductID() const {
204 uint16 product_id
= 0;
205 ParseModalias(object_path_
, NULL
, NULL
, &product_id
, NULL
);
209 uint16
BluetoothDeviceChromeOS::GetDeviceID() const {
210 uint16 device_id
= 0;
211 ParseModalias(object_path_
, NULL
, NULL
, NULL
, &device_id
);
215 bool BluetoothDeviceChromeOS::IsPaired() const {
216 BluetoothDeviceClient::Properties
* properties
=
217 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
218 GetProperties(object_path_
);
221 // Trusted devices are devices that don't support pairing but that the
222 // user has explicitly connected; it makes no sense for UI purposes to
223 // treat them differently from each other.
224 return properties
->paired
.value() || properties
->trusted
.value();
227 bool BluetoothDeviceChromeOS::IsConnected() const {
228 BluetoothDeviceClient::Properties
* properties
=
229 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
230 GetProperties(object_path_
);
233 return properties
->connected
.value();
236 bool BluetoothDeviceChromeOS::IsConnectable() const {
237 BluetoothInputClient::Properties
* input_properties
=
238 DBusThreadManager::Get()->GetBluetoothInputClient()->
239 GetProperties(object_path_
);
240 // GetProperties returns NULL when the device does not implement the given
241 // interface. Non HID devices are normally connectable.
242 if (!input_properties
)
245 return input_properties
->reconnect_mode
.value() != "device";
248 bool BluetoothDeviceChromeOS::IsConnecting() const {
249 return num_connecting_calls_
> 0;
252 BluetoothDeviceChromeOS::UUIDList
BluetoothDeviceChromeOS::GetUUIDs() const {
253 BluetoothDeviceClient::Properties
* properties
=
254 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
255 GetProperties(object_path_
);
258 std::vector
<device::BluetoothUUID
> uuids
;
259 const std::vector
<std::string
> &dbus_uuids
= properties
->uuids
.value();
260 for (std::vector
<std::string
>::const_iterator iter
= dbus_uuids
.begin();
261 iter
!= dbus_uuids
.end(); ++iter
) {
262 device::BluetoothUUID
uuid(*iter
);
263 DCHECK(uuid
.IsValid());
264 uuids
.push_back(uuid
);
269 bool BluetoothDeviceChromeOS::ExpectingPinCode() const {
270 return pairing_
.get() && pairing_
->ExpectingPinCode();
273 bool BluetoothDeviceChromeOS::ExpectingPasskey() const {
274 return pairing_
.get() && pairing_
->ExpectingPasskey();
277 bool BluetoothDeviceChromeOS::ExpectingConfirmation() const {
278 return pairing_
.get() && pairing_
->ExpectingConfirmation();
281 void BluetoothDeviceChromeOS::Connect(
282 BluetoothDevice::PairingDelegate
* pairing_delegate
,
283 const base::Closure
& callback
,
284 const ConnectErrorCallback
& error_callback
) {
285 if (num_connecting_calls_
++ == 0)
286 adapter_
->NotifyDeviceChanged(this);
288 VLOG(1) << object_path_
.value() << ": Connecting, " << num_connecting_calls_
291 if (IsPaired() || !pairing_delegate
|| !IsPairable()) {
292 // No need to pair, or unable to, skip straight to connection.
293 ConnectInternal(false, callback
, error_callback
);
295 // Initiate high-security connection with pairing.
296 BeginPairing(pairing_delegate
);
298 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
300 base::Bind(&BluetoothDeviceChromeOS::OnPair
,
301 weak_ptr_factory_
.GetWeakPtr(),
302 callback
, error_callback
),
303 base::Bind(&BluetoothDeviceChromeOS::OnPairError
,
304 weak_ptr_factory_
.GetWeakPtr(),
309 void BluetoothDeviceChromeOS::SetPinCode(const std::string
& pincode
) {
313 pairing_
->SetPinCode(pincode
);
316 void BluetoothDeviceChromeOS::SetPasskey(uint32 passkey
) {
320 pairing_
->SetPasskey(passkey
);
323 void BluetoothDeviceChromeOS::ConfirmPairing() {
327 pairing_
->ConfirmPairing();
330 void BluetoothDeviceChromeOS::RejectPairing() {
334 pairing_
->RejectPairing();
337 void BluetoothDeviceChromeOS::CancelPairing() {
338 bool canceled
= false;
340 // If there is a callback in progress that we can reply to then use that
341 // to cancel the current pairing request.
342 if (pairing_
.get() && pairing_
->CancelPairing())
345 // If not we have to send an explicit CancelPairing() to the device instead.
347 VLOG(1) << object_path_
.value() << ": No pairing context or callback. "
348 << "Sending explicit cancel";
349 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
352 base::Bind(&base::DoNothing
),
353 base::Bind(&BluetoothDeviceChromeOS::OnCancelPairingError
,
354 weak_ptr_factory_
.GetWeakPtr()));
357 // Since there is no callback to this method it's possible that the pairing
358 // delegate is going to be freed before things complete (indeed it's
359 // documented that this is the method you should call while freeing the
360 // pairing delegate), so clear our the context holding on to it.
364 void BluetoothDeviceChromeOS::Disconnect(const base::Closure
& callback
,
365 const ErrorCallback
& error_callback
) {
366 VLOG(1) << object_path_
.value() << ": Disconnecting";
367 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
370 base::Bind(&BluetoothDeviceChromeOS::OnDisconnect
,
371 weak_ptr_factory_
.GetWeakPtr(),
373 base::Bind(&BluetoothDeviceChromeOS::OnDisconnectError
,
374 weak_ptr_factory_
.GetWeakPtr(),
378 void BluetoothDeviceChromeOS::Forget(const ErrorCallback
& error_callback
) {
379 VLOG(1) << object_path_
.value() << ": Removing device";
380 DBusThreadManager::Get()->GetBluetoothAdapterClient()->
382 adapter_
->object_path_
,
384 base::Bind(&base::DoNothing
),
385 base::Bind(&BluetoothDeviceChromeOS::OnForgetError
,
386 weak_ptr_factory_
.GetWeakPtr(),
390 void BluetoothDeviceChromeOS::ConnectToProfile(
391 device::BluetoothProfile
* profile
,
392 const base::Closure
& callback
,
393 const ConnectToProfileErrorCallback
& error_callback
) {
394 BluetoothProfileChromeOS
* profile_chromeos
=
395 static_cast<BluetoothProfileChromeOS
*>(profile
);
396 VLOG(1) << object_path_
.value() << ": Connecting profile: "
397 << profile_chromeos
->uuid().canonical_value();
398 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
401 profile_chromeos
->uuid().canonical_value(),
403 &BluetoothDeviceChromeOS::OnConnectProfile
,
404 weak_ptr_factory_
.GetWeakPtr(),
408 &BluetoothDeviceChromeOS::OnConnectProfileError
,
409 weak_ptr_factory_
.GetWeakPtr(),
414 void BluetoothDeviceChromeOS::SetOutOfBandPairingData(
415 const device::BluetoothOutOfBandPairingData
& data
,
416 const base::Closure
& callback
,
417 const ErrorCallback
& error_callback
) {
418 // TODO(keybuk): implement
419 error_callback
.Run();
422 void BluetoothDeviceChromeOS::ClearOutOfBandPairingData(
423 const base::Closure
& callback
,
424 const ErrorCallback
& error_callback
) {
425 // TODO(keybuk): implement
426 error_callback
.Run();
429 BluetoothPairingChromeOS
* BluetoothDeviceChromeOS::BeginPairing(
430 BluetoothDevice::PairingDelegate
* pairing_delegate
) {
431 pairing_
.reset(new BluetoothPairingChromeOS(this, pairing_delegate
));
432 return pairing_
.get();
435 void BluetoothDeviceChromeOS::EndPairing() {
439 BluetoothPairingChromeOS
* BluetoothDeviceChromeOS::GetPairing() const {
440 return pairing_
.get();
443 void BluetoothDeviceChromeOS::GattServiceAdded(
444 const dbus::ObjectPath
& object_path
) {
445 if (GetGattService(object_path
.value())) {
446 VLOG(1) << "Remote GATT service already exists: " << object_path
.value();
450 BluetoothGattServiceClient::Properties
* properties
=
451 DBusThreadManager::Get()->GetBluetoothGattServiceClient()->
452 GetProperties(object_path
);
454 if (properties
->device
.value() != object_path_
) {
455 VLOG(2) << "Remote GATT service does not belong to this device.";
459 VLOG(1) << "Adding new remote GATT service for device: " << GetAddress();
461 BluetoothRemoteGattServiceChromeOS
* service
=
462 new BluetoothRemoteGattServiceChromeOS(this, object_path
);
463 gatt_services_
[service
->GetIdentifier()] = service
;
464 DCHECK(service
->object_path() == object_path
);
465 DCHECK(service
->GetUUID().IsValid());
467 FOR_EACH_OBSERVER(device::BluetoothDevice::Observer
, observers_
,
468 GattServiceAdded(this, service
));
471 void BluetoothDeviceChromeOS::GattServiceRemoved(
472 const dbus::ObjectPath
& object_path
) {
473 GattServiceMap::iterator iter
= gatt_services_
.find(object_path
.value());
474 if (iter
== gatt_services_
.end()) {
475 VLOG(2) << "Unknown GATT service removed: " << object_path
.value();
479 VLOG(1) << "Removing remote GATT service from device: " << GetAddress();
481 BluetoothRemoteGattServiceChromeOS
* service
=
482 static_cast<BluetoothRemoteGattServiceChromeOS
*>(iter
->second
);
483 DCHECK(service
->object_path() == object_path
);
484 gatt_services_
.erase(iter
);
485 FOR_EACH_OBSERVER(device::BluetoothDevice::Observer
, observers_
,
486 GattServiceRemoved(this, service
));
490 void BluetoothDeviceChromeOS::ConnectInternal(
492 const base::Closure
& callback
,
493 const ConnectErrorCallback
& error_callback
) {
494 VLOG(1) << object_path_
.value() << ": Connecting";
495 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
498 base::Bind(&BluetoothDeviceChromeOS::OnConnect
,
499 weak_ptr_factory_
.GetWeakPtr(),
502 base::Bind(&BluetoothDeviceChromeOS::OnConnectError
,
503 weak_ptr_factory_
.GetWeakPtr(),
508 void BluetoothDeviceChromeOS::OnConnect(bool after_pairing
,
509 const base::Closure
& callback
) {
510 if (--num_connecting_calls_
== 0)
511 adapter_
->NotifyDeviceChanged(this);
513 DCHECK(num_connecting_calls_
>= 0);
514 VLOG(1) << object_path_
.value() << ": Connected, " << num_connecting_calls_
515 << " still in progress";
520 UMA_HISTOGRAM_ENUMERATION("Bluetooth.PairingResult",
521 UMA_PAIRING_RESULT_SUCCESS
,
522 UMA_PAIRING_RESULT_COUNT
);
527 void BluetoothDeviceChromeOS::OnConnectError(
529 const ConnectErrorCallback
& error_callback
,
530 const std::string
& error_name
,
531 const std::string
& error_message
) {
532 if (--num_connecting_calls_
== 0)
533 adapter_
->NotifyDeviceChanged(this);
535 DCHECK(num_connecting_calls_
>= 0);
536 LOG(WARNING
) << object_path_
.value() << ": Failed to connect device: "
537 << error_name
<< ": " << error_message
;
538 VLOG(1) << object_path_
.value() << ": " << num_connecting_calls_
539 << " still in progress";
541 // Determine the error code from error_name.
542 ConnectErrorCode error_code
= ERROR_UNKNOWN
;
543 if (error_name
== bluetooth_device::kErrorFailed
) {
544 error_code
= ERROR_FAILED
;
545 } else if (error_name
== bluetooth_device::kErrorInProgress
) {
546 error_code
= ERROR_INPROGRESS
;
547 } else if (error_name
== bluetooth_device::kErrorNotSupported
) {
548 error_code
= ERROR_UNSUPPORTED_DEVICE
;
552 RecordPairingResult(error_code
);
553 error_callback
.Run(error_code
);
556 void BluetoothDeviceChromeOS::OnPair(
557 const base::Closure
& callback
,
558 const ConnectErrorCallback
& error_callback
) {
559 VLOG(1) << object_path_
.value() << ": Paired";
563 ConnectInternal(true, callback
, error_callback
);
566 void BluetoothDeviceChromeOS::OnPairError(
567 const ConnectErrorCallback
& error_callback
,
568 const std::string
& error_name
,
569 const std::string
& error_message
) {
570 if (--num_connecting_calls_
== 0)
571 adapter_
->NotifyDeviceChanged(this);
573 DCHECK(num_connecting_calls_
>= 0);
574 LOG(WARNING
) << object_path_
.value() << ": Failed to pair device: "
575 << error_name
<< ": " << error_message
;
576 VLOG(1) << object_path_
.value() << ": " << num_connecting_calls_
577 << " still in progress";
581 // Determine the error code from error_name.
582 ConnectErrorCode error_code
= ERROR_UNKNOWN
;
583 if (error_name
== bluetooth_device::kErrorConnectionAttemptFailed
) {
584 error_code
= ERROR_FAILED
;
585 } else if (error_name
== bluetooth_device::kErrorFailed
) {
586 error_code
= ERROR_FAILED
;
587 } else if (error_name
== bluetooth_device::kErrorAuthenticationFailed
) {
588 error_code
= ERROR_AUTH_FAILED
;
589 } else if (error_name
== bluetooth_device::kErrorAuthenticationCanceled
) {
590 error_code
= ERROR_AUTH_CANCELED
;
591 } else if (error_name
== bluetooth_device::kErrorAuthenticationRejected
) {
592 error_code
= ERROR_AUTH_REJECTED
;
593 } else if (error_name
== bluetooth_device::kErrorAuthenticationTimeout
) {
594 error_code
= ERROR_AUTH_TIMEOUT
;
597 RecordPairingResult(error_code
);
598 error_callback
.Run(error_code
);
601 void BluetoothDeviceChromeOS::OnCancelPairingError(
602 const std::string
& error_name
,
603 const std::string
& error_message
) {
604 LOG(WARNING
) << object_path_
.value() << ": Failed to cancel pairing: "
605 << error_name
<< ": " << error_message
;
608 void BluetoothDeviceChromeOS::SetTrusted() {
609 // Unconditionally send the property change, rather than checking the value
610 // first; there's no harm in doing this and it solves any race conditions
611 // with the property becoming true or false and this call happening before
612 // we get the D-Bus signal about the earlier change.
613 DBusThreadManager::Get()->GetBluetoothDeviceClient()->
614 GetProperties(object_path_
)->trusted
.Set(
616 base::Bind(&BluetoothDeviceChromeOS::OnSetTrusted
,
617 weak_ptr_factory_
.GetWeakPtr()));
620 void BluetoothDeviceChromeOS::OnSetTrusted(bool success
) {
621 LOG_IF(WARNING
, !success
) << object_path_
.value()
622 << ": Failed to set device as trusted";
625 void BluetoothDeviceChromeOS::OnDisconnect(const base::Closure
& callback
) {
626 VLOG(1) << object_path_
.value() << ": Disconnected";
630 void BluetoothDeviceChromeOS::OnDisconnectError(
631 const ErrorCallback
& error_callback
,
632 const std::string
& error_name
,
633 const std::string
& error_message
) {
634 LOG(WARNING
) << object_path_
.value() << ": Failed to disconnect device: "
635 << error_name
<< ": " << error_message
;
636 error_callback
.Run();
639 void BluetoothDeviceChromeOS::OnForgetError(
640 const ErrorCallback
& error_callback
,
641 const std::string
& error_name
,
642 const std::string
& error_message
) {
643 LOG(WARNING
) << object_path_
.value() << ": Failed to remove device: "
644 << error_name
<< ": " << error_message
;
645 error_callback
.Run();
648 void BluetoothDeviceChromeOS::OnConnectProfile(
649 device::BluetoothProfile
* profile
,
650 const base::Closure
& callback
) {
651 BluetoothProfileChromeOS
* profile_chromeos
=
652 static_cast<BluetoothProfileChromeOS
*>(profile
);
653 VLOG(1) << object_path_
.value() << ": Profile connected: "
654 << profile_chromeos
->uuid().canonical_value();
658 void BluetoothDeviceChromeOS::OnConnectProfileError(
659 device::BluetoothProfile
* profile
,
660 const ConnectToProfileErrorCallback
& error_callback
,
661 const std::string
& error_name
,
662 const std::string
& error_message
) {
663 BluetoothProfileChromeOS
* profile_chromeos
=
664 static_cast<BluetoothProfileChromeOS
*>(profile
);
665 VLOG(1) << object_path_
.value() << ": Profile connection failed: "
666 << profile_chromeos
->uuid().canonical_value() << ": "
667 << error_name
<< ": " << error_message
;
668 error_callback
.Run(error_message
);
671 } // namespace chromeos