1 // Copyright 2014 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 "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h"
8 #include "base/location.h"
9 #include "base/rand_util.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/time/time.h"
13 #include "chromeos/dbus/dbus_thread_manager.h"
14 #include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h"
15 #include "third_party/cros_system_api/dbus/service_constants.h"
21 const int kStartNotifyResponseIntervalMs
= 200;
22 const int kHeartRateMeasurementNotificationIntervalMs
= 2000;
26 FakeBluetoothGattCharacteristicClient::DelayedCallback::DelayedCallback(
27 base::Closure callback
,
29 : callback_(callback
), delay_(delay
) {
32 FakeBluetoothGattCharacteristicClient::DelayedCallback::~DelayedCallback() {
36 const char FakeBluetoothGattCharacteristicClient::
37 kHeartRateMeasurementPathComponent
[] = "char0000";
38 const char FakeBluetoothGattCharacteristicClient::
39 kBodySensorLocationPathComponent
[] = "char0001";
40 const char FakeBluetoothGattCharacteristicClient::
41 kHeartRateControlPointPathComponent
[] = "char0002";
44 const char FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID
[] =
45 "00002a37-0000-1000-8000-00805f9b34fb";
46 const char FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID
[] =
47 "00002a38-0000-1000-8000-00805f9b34fb";
48 const char FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID
[] =
49 "00002a39-0000-1000-8000-00805f9b34fb";
51 FakeBluetoothGattCharacteristicClient::Properties::Properties(
52 const PropertyChangedCallback
& callback
)
53 : BluetoothGattCharacteristicClient::Properties(
55 bluetooth_gatt_characteristic::kBluetoothGattCharacteristicInterface
,
59 FakeBluetoothGattCharacteristicClient::Properties::~Properties() {
62 void FakeBluetoothGattCharacteristicClient::Properties::Get(
63 dbus::PropertyBase
* property
,
64 dbus::PropertySet::GetCallback callback
) {
65 VLOG(1) << "Get " << property
->name();
69 void FakeBluetoothGattCharacteristicClient::Properties::GetAll() {
73 void FakeBluetoothGattCharacteristicClient::Properties::Set(
74 dbus::PropertyBase
* property
,
75 dbus::PropertySet::SetCallback callback
) {
76 VLOG(1) << "Set " << property
->name();
80 FakeBluetoothGattCharacteristicClient::FakeBluetoothGattCharacteristicClient()
81 : heart_rate_visible_(false),
86 weak_ptr_factory_(this) {
89 FakeBluetoothGattCharacteristicClient::
90 ~FakeBluetoothGattCharacteristicClient() {
91 for (const auto& it
: action_extra_requests_
) {
94 action_extra_requests_
.clear();
97 void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus
* bus
) {
100 void FakeBluetoothGattCharacteristicClient::AddObserver(Observer
* observer
) {
101 observers_
.AddObserver(observer
);
104 void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer
* observer
) {
105 observers_
.RemoveObserver(observer
);
108 std::vector
<dbus::ObjectPath
>
109 FakeBluetoothGattCharacteristicClient::GetCharacteristics() {
110 std::vector
<dbus::ObjectPath
> paths
;
111 if (IsHeartRateVisible()) {
112 paths
.push_back(dbus::ObjectPath(heart_rate_measurement_path_
));
113 paths
.push_back(dbus::ObjectPath(body_sensor_location_path_
));
114 paths
.push_back(dbus::ObjectPath(heart_rate_control_point_path_
));
119 FakeBluetoothGattCharacteristicClient::Properties
*
120 FakeBluetoothGattCharacteristicClient::GetProperties(
121 const dbus::ObjectPath
& object_path
) {
122 if (object_path
.value() == heart_rate_measurement_path_
) {
123 DCHECK(heart_rate_measurement_properties_
.get());
124 return heart_rate_measurement_properties_
.get();
126 if (object_path
.value() == body_sensor_location_path_
) {
127 DCHECK(body_sensor_location_properties_
.get());
128 return body_sensor_location_properties_
.get();
130 if (object_path
.value() == heart_rate_control_point_path_
) {
131 DCHECK(heart_rate_control_point_properties_
.get());
132 return heart_rate_control_point_properties_
.get();
137 void FakeBluetoothGattCharacteristicClient::ReadValue(
138 const dbus::ObjectPath
& object_path
,
139 const ValueCallback
& callback
,
140 const ErrorCallback
& error_callback
) {
141 if (!authenticated_
) {
142 error_callback
.Run("org.bluez.Error.NotPaired", "Please login");
147 error_callback
.Run("org.bluez.Error.NotAuthorized", "Authorize first");
151 if (object_path
.value() == heart_rate_control_point_path_
) {
152 error_callback
.Run("org.bluez.Error.NotPermitted",
153 "Reads of this value are not allowed");
157 if (object_path
.value() == heart_rate_measurement_path_
) {
158 error_callback
.Run("org.bluez.Error.NotSupported",
159 "Action not supported on this characteristic");
163 if (object_path
.value() != body_sensor_location_path_
) {
164 error_callback
.Run(kUnknownCharacteristicError
, "");
168 if (action_extra_requests_
.find("ReadValue") !=
169 action_extra_requests_
.end()) {
170 DelayedCallback
* delayed
= action_extra_requests_
["ReadValue"];
172 error_callback
.Run("org.bluez.Error.InProgress",
173 "Another read is currenty in progress");
174 if (delayed
->delay_
== 0) {
175 delayed
->callback_
.Run();
176 action_extra_requests_
.erase("ReadValue");
182 base::Closure completed_callback
;
183 if (!IsHeartRateVisible()) {
185 base::Bind(error_callback
, kUnknownCharacteristicError
, "");
187 std::vector
<uint8
> value
= {0x06}; // Location is "foot".
188 completed_callback
= base::Bind(
189 &FakeBluetoothGattCharacteristicClient::DelayedReadValueCallback
,
190 weak_ptr_factory_
.GetWeakPtr(), object_path
, callback
, value
);
193 if (extra_requests_
> 0) {
194 action_extra_requests_
["ReadValue"] =
195 new DelayedCallback(completed_callback
, extra_requests_
);
199 completed_callback
.Run();
202 void FakeBluetoothGattCharacteristicClient::WriteValue(
203 const dbus::ObjectPath
& object_path
,
204 const std::vector
<uint8
>& value
,
205 const base::Closure
& callback
,
206 const ErrorCallback
& error_callback
) {
207 if (!authenticated_
) {
208 error_callback
.Run("org.bluez.Error.NotPaired", "Please login");
213 error_callback
.Run("org.bluez.Error.NotAuthorized", "Authorize first");
217 if (!IsHeartRateVisible()) {
218 error_callback
.Run(kUnknownCharacteristicError
, "");
222 if (object_path
.value() == heart_rate_measurement_path_
) {
223 error_callback
.Run("org.bluez.Error.NotSupported",
224 "Action not supported on this characteristic");
228 if (object_path
.value() != heart_rate_control_point_path_
) {
229 error_callback
.Run("org.bluez.Error.NotPermitted",
230 "Writes of this value are not allowed");
234 DCHECK(heart_rate_control_point_properties_
.get());
235 if (action_extra_requests_
.find("WriteValue") !=
236 action_extra_requests_
.end()) {
237 DelayedCallback
* delayed
= action_extra_requests_
["WriteValue"];
239 error_callback
.Run("org.bluez.Error.InProgress",
240 "Another write is in progress");
241 if (delayed
->delay_
== 0) {
242 delayed
->callback_
.Run();
243 action_extra_requests_
.erase("WriteValue");
248 base::Closure completed_callback
;
249 if (value
.size() != 1) {
250 completed_callback
= base::Bind(error_callback
,
251 "org.bluez.Error.InvalidValueLength",
252 "Invalid length for write");
253 } else if (value
[0] > 1) {
254 completed_callback
= base::Bind(error_callback
,
255 "org.bluez.Error.Failed",
256 "Invalid value given for write");
257 } else if (value
[0] == 1) {
258 // TODO(jamuraa): make this happen when the callback happens
259 calories_burned_
= 0;
260 completed_callback
= callback
;
263 if (extra_requests_
> 0) {
264 action_extra_requests_
["WriteValue"] =
265 new DelayedCallback(completed_callback
, extra_requests_
);
268 completed_callback
.Run();
271 void FakeBluetoothGattCharacteristicClient::StartNotify(
272 const dbus::ObjectPath
& object_path
,
273 const base::Closure
& callback
,
274 const ErrorCallback
& error_callback
) {
275 if (!IsHeartRateVisible()) {
276 error_callback
.Run(kUnknownCharacteristicError
, "");
280 if (object_path
.value() != heart_rate_measurement_path_
) {
281 error_callback
.Run("org.bluez.Error.NotSupported",
282 "This characteristic does not support notifications");
286 if (heart_rate_measurement_properties_
->notifying
.value()) {
287 error_callback
.Run("org.bluez.Error.InProgress",
288 "Characteristic already notifying");
292 heart_rate_measurement_properties_
->notifying
.ReplaceValue(true);
293 ScheduleHeartRateMeasurementValueChange();
295 // Respond asynchronously.
296 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
298 base::TimeDelta::FromMilliseconds(kStartNotifyResponseIntervalMs
));
301 void FakeBluetoothGattCharacteristicClient::StopNotify(
302 const dbus::ObjectPath
& object_path
,
303 const base::Closure
& callback
,
304 const ErrorCallback
& error_callback
) {
305 if (!IsHeartRateVisible()) {
306 error_callback
.Run(kUnknownCharacteristicError
, "");
310 if (object_path
.value() != heart_rate_measurement_path_
) {
311 error_callback
.Run("org.bluez.Error.NotSupported",
312 "This characteristic does not support notifications");
316 if (!heart_rate_measurement_properties_
->notifying
.value()) {
317 error_callback
.Run("org.bluez.Error.Failed", "Not notifying");
321 heart_rate_measurement_properties_
->notifying
.ReplaceValue(false);
326 void FakeBluetoothGattCharacteristicClient::ExposeHeartRateCharacteristics(
327 const dbus::ObjectPath
& service_path
) {
328 if (IsHeartRateVisible()) {
329 VLOG(2) << "Fake Heart Rate characteristics are already visible.";
333 VLOG(2) << "Exposing fake Heart Rate characteristics.";
335 std::vector
<std::string
> flags
;
337 // ==== Heart Rate Measurement Characteristic ====
338 heart_rate_measurement_path_
=
339 service_path
.value() + "/" + kHeartRateMeasurementPathComponent
;
340 heart_rate_measurement_properties_
.reset(new Properties(base::Bind(
341 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
342 weak_ptr_factory_
.GetWeakPtr(),
343 dbus::ObjectPath(heart_rate_measurement_path_
))));
344 heart_rate_measurement_properties_
->uuid
.ReplaceValue(
345 kHeartRateMeasurementUUID
);
346 heart_rate_measurement_properties_
->service
.ReplaceValue(service_path
);
347 flags
.push_back(bluetooth_gatt_characteristic::kFlagNotify
);
348 heart_rate_measurement_properties_
->flags
.ReplaceValue(flags
);
350 // ==== Body Sensor Location Characteristic ====
351 body_sensor_location_path_
=
352 service_path
.value() + "/" + kBodySensorLocationPathComponent
;
353 body_sensor_location_properties_
.reset(new Properties(base::Bind(
354 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
355 weak_ptr_factory_
.GetWeakPtr(),
356 dbus::ObjectPath(body_sensor_location_path_
))));
357 body_sensor_location_properties_
->uuid
.ReplaceValue(kBodySensorLocationUUID
);
358 body_sensor_location_properties_
->service
.ReplaceValue(service_path
);
360 flags
.push_back(bluetooth_gatt_characteristic::kFlagRead
);
361 body_sensor_location_properties_
->flags
.ReplaceValue(flags
);
363 // ==== Heart Rate Control Point Characteristic ====
364 heart_rate_control_point_path_
=
365 service_path
.value() + "/" + kHeartRateControlPointPathComponent
;
366 heart_rate_control_point_properties_
.reset(new Properties(base::Bind(
367 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
368 weak_ptr_factory_
.GetWeakPtr(),
369 dbus::ObjectPath(heart_rate_control_point_path_
))));
370 heart_rate_control_point_properties_
->uuid
.ReplaceValue(
371 kHeartRateControlPointUUID
);
372 heart_rate_control_point_properties_
->service
.ReplaceValue(service_path
);
374 flags
.push_back(bluetooth_gatt_characteristic::kFlagWrite
);
375 heart_rate_control_point_properties_
->flags
.ReplaceValue(flags
);
377 heart_rate_visible_
= true;
379 NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_measurement_path_
));
380 NotifyCharacteristicAdded(dbus::ObjectPath(body_sensor_location_path_
));
381 NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_control_point_path_
));
383 // Expose CCC descriptor for Heart Rate Measurement characteristic.
384 FakeBluetoothGattDescriptorClient
* descriptor_client
=
385 static_cast<FakeBluetoothGattDescriptorClient
*>(
386 DBusThreadManager::Get()->GetBluetoothGattDescriptorClient());
387 dbus::ObjectPath
ccc_path(descriptor_client
->ExposeDescriptor(
388 dbus::ObjectPath(heart_rate_measurement_path_
),
389 FakeBluetoothGattDescriptorClient::
390 kClientCharacteristicConfigurationUUID
));
391 DCHECK(ccc_path
.IsValid());
392 heart_rate_measurement_ccc_desc_path_
= ccc_path
.value();
394 std::vector
<dbus::ObjectPath
> desc_paths
;
395 desc_paths
.push_back(ccc_path
);
397 heart_rate_measurement_properties_
->descriptors
.ReplaceValue(desc_paths
);
400 void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() {
401 VLOG(2) << "Hiding fake Heart Rate characteristics.";
403 // Hide the descriptors.
404 FakeBluetoothGattDescriptorClient
* descriptor_client
=
405 static_cast<FakeBluetoothGattDescriptorClient
*>(
406 DBusThreadManager::Get()->GetBluetoothGattDescriptorClient());
407 descriptor_client
->HideDescriptor(
408 dbus::ObjectPath(heart_rate_measurement_ccc_desc_path_
));
410 // Notify the observers before deleting the properties structures so that they
411 // can be accessed from the observer method.
412 NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_measurement_path_
));
413 NotifyCharacteristicRemoved(dbus::ObjectPath(body_sensor_location_path_
));
414 NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_control_point_path_
));
416 heart_rate_measurement_properties_
.reset();
417 body_sensor_location_properties_
.reset();
418 heart_rate_control_point_properties_
.reset();
420 heart_rate_measurement_path_
.clear();
421 body_sensor_location_path_
.clear();
422 heart_rate_control_point_path_
.clear();
423 heart_rate_visible_
= false;
426 void FakeBluetoothGattCharacteristicClient::SetExtraProcessing(
428 extra_requests_
= requests
;
429 if (extra_requests_
== 0) {
430 for (const auto& it
: action_extra_requests_
) {
431 it
.second
->callback_
.Run();
434 action_extra_requests_
.clear();
437 VLOG(2) << "Requests SLOW now, " << requests
<< " InProgress errors each.";
440 size_t FakeBluetoothGattCharacteristicClient::GetExtraProcessing() const {
441 return extra_requests_
;
445 FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementPath() const {
446 return dbus::ObjectPath(heart_rate_measurement_path_
);
450 FakeBluetoothGattCharacteristicClient::GetBodySensorLocationPath() const {
451 return dbus::ObjectPath(body_sensor_location_path_
);
455 FakeBluetoothGattCharacteristicClient::GetHeartRateControlPointPath() const {
456 return dbus::ObjectPath(heart_rate_control_point_path_
);
459 void FakeBluetoothGattCharacteristicClient::OnPropertyChanged(
460 const dbus::ObjectPath
& object_path
,
461 const std::string
& property_name
) {
462 VLOG(2) << "Characteristic property changed: " << object_path
.value()
463 << ": " << property_name
;
465 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
466 GattCharacteristicPropertyChanged(
467 object_path
, property_name
));
470 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded(
471 const dbus::ObjectPath
& object_path
) {
472 VLOG(2) << "GATT characteristic added: " << object_path
.value();
473 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
474 GattCharacteristicAdded(object_path
));
477 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved(
478 const dbus::ObjectPath
& object_path
) {
479 VLOG(2) << "GATT characteristic removed: " << object_path
.value();
480 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
481 GattCharacteristicRemoved(object_path
));
484 void FakeBluetoothGattCharacteristicClient::
485 ScheduleHeartRateMeasurementValueChange() {
486 if (!IsHeartRateVisible())
489 // Don't send updates if the characteristic is not notifying.
490 if (!heart_rate_measurement_properties_
->notifying
.value())
493 VLOG(2) << "Updating heart rate value.";
494 std::vector
<uint8
> measurement
= GetHeartRateMeasurementValue();
495 heart_rate_measurement_properties_
->value
.ReplaceValue(measurement
);
497 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
498 FROM_HERE
, base::Bind(&FakeBluetoothGattCharacteristicClient::
499 ScheduleHeartRateMeasurementValueChange
,
500 weak_ptr_factory_
.GetWeakPtr()),
501 base::TimeDelta::FromMilliseconds(
502 kHeartRateMeasurementNotificationIntervalMs
));
505 void FakeBluetoothGattCharacteristicClient::DelayedReadValueCallback(
506 const dbus::ObjectPath
& object_path
,
507 const ValueCallback
& callback
,
508 const std::vector
<uint8_t>& value
) {
509 Properties
* properties
= GetProperties(object_path
);
512 properties
->value
.ReplaceValue(value
);
517 FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementValue() {
518 // TODO(armansito): We should make sure to properly pack this struct to ensure
519 // correct byte alignment and endianness. It doesn't matter too much right now
520 // as this is a fake and GCC on Linux seems to do the right thing.
524 uint16 energy_expanded
;
528 // Flags in LSB: 0 11 1 1 000
530 // 8-bit bpm format -- | | | |
531 // Sensor contact supported -- | | |
532 // Energy expanded field present -- | |
533 // RR-Interval values present ------- |
534 // Reserved for future use ------------
536 value
.flags
|= (0x03 << 1);
537 value
.flags
|= (0x01 << 3);
538 value
.flags
|= (0x01 << 4);
540 // Pick a value between 117 bpm and 153 bpm for heart rate.
541 value
.bpm
= static_cast<uint8
>(base::RandInt(117, 153));
543 // Total calories burned in kJoules since the last reset. Increment this by 1
544 // every time. It's fine if it overflows: it becomes 0 when the user resets
545 // the heart rate monitor (or pretend that he had a lot of cheeseburgers).
546 value
.energy_expanded
= calories_burned_
++;
548 // Include one RR-Interval value, in seconds.
549 value
.rr_interval
= 60/value
.bpm
;
551 // Return the bytes in an array.
552 uint8
* bytes
= reinterpret_cast<uint8
*>(&value
);
553 std::vector
<uint8
> return_value
;
554 return_value
.assign(bytes
, bytes
+ sizeof(value
));
558 bool FakeBluetoothGattCharacteristicClient::IsHeartRateVisible() const {
559 DCHECK(heart_rate_visible_
!= heart_rate_measurement_path_
.empty());
560 DCHECK(heart_rate_visible_
!= body_sensor_location_path_
.empty());
561 DCHECK(heart_rate_visible_
!= heart_rate_control_point_path_
.empty());
562 DCHECK(heart_rate_visible_
== !!heart_rate_measurement_properties_
.get());
563 DCHECK(heart_rate_visible_
== !!body_sensor_location_properties_
.get());
564 DCHECK(heart_rate_visible_
== !!heart_rate_control_point_properties_
.get());
565 return heart_rate_visible_
;
568 } // namespace chromeos