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/message_loop/message_loop.h"
9 #include "base/rand_util.h"
10 #include "base/time/time.h"
11 #include "chromeos/dbus/dbus_thread_manager.h"
12 #include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h"
13 #include "third_party/cros_system_api/dbus/service_constants.h"
19 const int kStartNotifyResponseIntervalMs
= 200;
20 const int kHeartRateMeasurementNotificationIntervalMs
= 2000;
24 FakeBluetoothGattCharacteristicClient::DelayedCallback::DelayedCallback(
25 base::Closure callback
,
27 : callback_(callback
), delay_(delay
) {
30 FakeBluetoothGattCharacteristicClient::DelayedCallback::~DelayedCallback() {
34 const char FakeBluetoothGattCharacteristicClient::
35 kHeartRateMeasurementPathComponent
[] = "char0000";
36 const char FakeBluetoothGattCharacteristicClient::
37 kBodySensorLocationPathComponent
[] = "char0001";
38 const char FakeBluetoothGattCharacteristicClient::
39 kHeartRateControlPointPathComponent
[] = "char0002";
42 const char FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID
[] =
43 "00002a37-0000-1000-8000-00805f9b34fb";
44 const char FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID
[] =
45 "00002a38-0000-1000-8000-00805f9b34fb";
46 const char FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID
[] =
47 "00002a39-0000-1000-8000-00805f9b34fb";
49 FakeBluetoothGattCharacteristicClient::Properties::Properties(
50 const PropertyChangedCallback
& callback
)
51 : BluetoothGattCharacteristicClient::Properties(
53 bluetooth_gatt_characteristic::kBluetoothGattCharacteristicInterface
,
57 FakeBluetoothGattCharacteristicClient::Properties::~Properties() {
60 void FakeBluetoothGattCharacteristicClient::Properties::Get(
61 dbus::PropertyBase
* property
,
62 dbus::PropertySet::GetCallback callback
) {
63 VLOG(1) << "Get " << property
->name();
67 void FakeBluetoothGattCharacteristicClient::Properties::GetAll() {
71 void FakeBluetoothGattCharacteristicClient::Properties::Set(
72 dbus::PropertyBase
* property
,
73 dbus::PropertySet::SetCallback callback
) {
74 VLOG(1) << "Set " << property
->name();
78 FakeBluetoothGattCharacteristicClient::FakeBluetoothGattCharacteristicClient()
79 : heart_rate_visible_(false),
84 weak_ptr_factory_(this) {
87 FakeBluetoothGattCharacteristicClient::
88 ~FakeBluetoothGattCharacteristicClient() {
89 for (const auto& it
: action_extra_requests_
) {
92 action_extra_requests_
.clear();
95 void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus
* bus
) {
98 void FakeBluetoothGattCharacteristicClient::AddObserver(Observer
* observer
) {
99 observers_
.AddObserver(observer
);
102 void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer
* observer
) {
103 observers_
.RemoveObserver(observer
);
106 std::vector
<dbus::ObjectPath
>
107 FakeBluetoothGattCharacteristicClient::GetCharacteristics() {
108 std::vector
<dbus::ObjectPath
> paths
;
109 if (IsHeartRateVisible()) {
110 paths
.push_back(dbus::ObjectPath(heart_rate_measurement_path_
));
111 paths
.push_back(dbus::ObjectPath(body_sensor_location_path_
));
112 paths
.push_back(dbus::ObjectPath(heart_rate_control_point_path_
));
117 FakeBluetoothGattCharacteristicClient::Properties
*
118 FakeBluetoothGattCharacteristicClient::GetProperties(
119 const dbus::ObjectPath
& object_path
) {
120 if (object_path
.value() == heart_rate_measurement_path_
) {
121 DCHECK(heart_rate_measurement_properties_
.get());
122 return heart_rate_measurement_properties_
.get();
124 if (object_path
.value() == body_sensor_location_path_
) {
125 DCHECK(body_sensor_location_properties_
.get());
126 return body_sensor_location_properties_
.get();
128 if (object_path
.value() == heart_rate_control_point_path_
) {
129 DCHECK(heart_rate_control_point_properties_
.get());
130 return heart_rate_control_point_properties_
.get();
135 void FakeBluetoothGattCharacteristicClient::ReadValue(
136 const dbus::ObjectPath
& object_path
,
137 const ValueCallback
& callback
,
138 const ErrorCallback
& error_callback
) {
139 if (!authenticated_
) {
140 error_callback
.Run("org.bluez.Error.NotPaired", "Please login");
145 error_callback
.Run("org.bluez.Error.NotAuthorized", "Authorize first");
149 if (object_path
.value() == heart_rate_control_point_path_
) {
150 error_callback
.Run("org.bluez.Error.ReadNotPermitted",
151 "Reads of this value are not allowed");
155 if (object_path
.value() == heart_rate_measurement_path_
) {
156 error_callback
.Run("org.bluez.Error.NotSupported",
157 "Action not supported on this characteristic");
161 if (object_path
.value() != body_sensor_location_path_
) {
162 error_callback
.Run(kUnknownCharacteristicError
, "");
166 if (action_extra_requests_
.find("ReadValue") !=
167 action_extra_requests_
.end()) {
168 DelayedCallback
* delayed
= action_extra_requests_
["ReadValue"];
170 error_callback
.Run("org.bluez.Error.InProgress",
171 "Another read is currenty in progress");
172 if (delayed
->delay_
== 0) {
173 delayed
->callback_
.Run();
174 action_extra_requests_
.erase("ReadValue");
179 base::Closure completed_callback
;
180 if (!IsHeartRateVisible()) {
182 base::Bind(error_callback
, kUnknownCharacteristicError
, "");
184 std::vector
<uint8
> value
;
185 value
.push_back(0x06); // Location is "foot".
186 completed_callback
= base::Bind(callback
, value
);
189 if (extra_requests_
> 0) {
190 action_extra_requests_
["ReadValue"] =
191 new DelayedCallback(completed_callback
, extra_requests_
);
194 completed_callback
.Run();
197 void FakeBluetoothGattCharacteristicClient::WriteValue(
198 const dbus::ObjectPath
& object_path
,
199 const std::vector
<uint8
>& value
,
200 const base::Closure
& callback
,
201 const ErrorCallback
& error_callback
) {
202 if (!authenticated_
) {
203 error_callback
.Run("org.bluez.Error.NotPaired", "Please login");
208 error_callback
.Run("org.bluez.Error.NotAuthorized", "Authorize first");
212 if (!IsHeartRateVisible()) {
213 error_callback
.Run(kUnknownCharacteristicError
, "");
217 if (object_path
.value() == heart_rate_measurement_path_
) {
218 error_callback
.Run("org.bluez.Error.NotSupported",
219 "Action not supported on this characteristic");
223 if (object_path
.value() != heart_rate_control_point_path_
) {
224 error_callback
.Run("org.bluez.Error.WriteNotPermitted",
225 "Writes of this value are not allowed");
229 DCHECK(heart_rate_control_point_properties_
.get());
230 if (action_extra_requests_
.find("WriteValue") !=
231 action_extra_requests_
.end()) {
232 DelayedCallback
* delayed
= action_extra_requests_
["WriteValue"];
234 error_callback
.Run("org.bluez.Error.InProgress",
235 "Another write is in progress");
236 if (delayed
->delay_
== 0) {
237 delayed
->callback_
.Run();
238 action_extra_requests_
.erase("WriteValue");
243 base::Closure completed_callback
;
244 if (value
.size() != 1) {
245 completed_callback
= base::Bind(error_callback
,
246 "org.bluez.Error.InvalidValueLength",
247 "Invalid length for write");
248 } else if (value
[0] > 1) {
249 completed_callback
= base::Bind(error_callback
,
250 "org.bluez.Error.Failed",
251 "Invalid value given for write");
252 } else if (value
[0] == 1) {
253 // TODO(jamuraa): make this happen when the callback happens
254 calories_burned_
= 0;
255 completed_callback
= callback
;
258 if (extra_requests_
> 0) {
259 action_extra_requests_
["WriteValue"] =
260 new DelayedCallback(completed_callback
, extra_requests_
);
263 completed_callback
.Run();
266 void FakeBluetoothGattCharacteristicClient::StartNotify(
267 const dbus::ObjectPath
& object_path
,
268 const base::Closure
& callback
,
269 const ErrorCallback
& error_callback
) {
270 if (!IsHeartRateVisible()) {
271 error_callback
.Run(kUnknownCharacteristicError
, "");
275 if (object_path
.value() != heart_rate_measurement_path_
) {
276 error_callback
.Run("org.bluez.Error.NotSupported",
277 "This characteristic does not support notifications");
281 if (heart_rate_measurement_properties_
->notifying
.value()) {
282 error_callback
.Run("org.bluez.Error.InProgress",
283 "Characteristic already notifying");
287 heart_rate_measurement_properties_
->notifying
.ReplaceValue(true);
288 ScheduleHeartRateMeasurementValueChange();
290 // Respond asynchronously.
291 base::MessageLoop::current()->PostDelayedTask(
294 base::TimeDelta::FromMilliseconds(kStartNotifyResponseIntervalMs
));
297 void FakeBluetoothGattCharacteristicClient::StopNotify(
298 const dbus::ObjectPath
& object_path
,
299 const base::Closure
& callback
,
300 const ErrorCallback
& error_callback
) {
301 if (!IsHeartRateVisible()) {
302 error_callback
.Run(kUnknownCharacteristicError
, "");
306 if (object_path
.value() != heart_rate_measurement_path_
) {
307 error_callback
.Run("org.bluez.Error.NotSupported",
308 "This characteristic does not support notifications");
312 if (!heart_rate_measurement_properties_
->notifying
.value()) {
313 error_callback
.Run("org.bluez.Error.Failed", "Not notifying");
317 heart_rate_measurement_properties_
->notifying
.ReplaceValue(false);
322 void FakeBluetoothGattCharacteristicClient::ExposeHeartRateCharacteristics(
323 const dbus::ObjectPath
& service_path
) {
324 if (IsHeartRateVisible()) {
325 VLOG(2) << "Fake Heart Rate characteristics are already visible.";
329 VLOG(2) << "Exposing fake Heart Rate characteristics.";
331 std::vector
<std::string
> flags
;
333 // ==== Heart Rate Measurement Characteristic ====
334 heart_rate_measurement_path_
=
335 service_path
.value() + "/" + kHeartRateMeasurementPathComponent
;
336 heart_rate_measurement_properties_
.reset(new Properties(base::Bind(
337 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
338 weak_ptr_factory_
.GetWeakPtr(),
339 dbus::ObjectPath(heart_rate_measurement_path_
))));
340 heart_rate_measurement_properties_
->uuid
.ReplaceValue(
341 kHeartRateMeasurementUUID
);
342 heart_rate_measurement_properties_
->service
.ReplaceValue(service_path
);
343 flags
.push_back(bluetooth_gatt_characteristic::kFlagNotify
);
344 heart_rate_measurement_properties_
->flags
.ReplaceValue(flags
);
346 // ==== Body Sensor Location Characteristic ====
347 body_sensor_location_path_
=
348 service_path
.value() + "/" + kBodySensorLocationPathComponent
;
349 body_sensor_location_properties_
.reset(new Properties(base::Bind(
350 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
351 weak_ptr_factory_
.GetWeakPtr(),
352 dbus::ObjectPath(body_sensor_location_path_
))));
353 body_sensor_location_properties_
->uuid
.ReplaceValue(kBodySensorLocationUUID
);
354 body_sensor_location_properties_
->service
.ReplaceValue(service_path
);
356 flags
.push_back(bluetooth_gatt_characteristic::kFlagRead
);
357 body_sensor_location_properties_
->flags
.ReplaceValue(flags
);
359 // ==== Heart Rate Control Point Characteristic ====
360 heart_rate_control_point_path_
=
361 service_path
.value() + "/" + kHeartRateControlPointPathComponent
;
362 heart_rate_control_point_properties_
.reset(new Properties(base::Bind(
363 &FakeBluetoothGattCharacteristicClient::OnPropertyChanged
,
364 weak_ptr_factory_
.GetWeakPtr(),
365 dbus::ObjectPath(heart_rate_control_point_path_
))));
366 heart_rate_control_point_properties_
->uuid
.ReplaceValue(
367 kHeartRateControlPointUUID
);
368 heart_rate_control_point_properties_
->service
.ReplaceValue(service_path
);
370 flags
.push_back(bluetooth_gatt_characteristic::kFlagWrite
);
371 heart_rate_control_point_properties_
->flags
.ReplaceValue(flags
);
373 heart_rate_visible_
= true;
375 NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_measurement_path_
));
376 NotifyCharacteristicAdded(dbus::ObjectPath(body_sensor_location_path_
));
377 NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_control_point_path_
));
379 // Expose CCC descriptor for Heart Rate Measurement characteristic.
380 FakeBluetoothGattDescriptorClient
* descriptor_client
=
381 static_cast<FakeBluetoothGattDescriptorClient
*>(
382 DBusThreadManager::Get()->GetBluetoothGattDescriptorClient());
383 dbus::ObjectPath
ccc_path(descriptor_client
->ExposeDescriptor(
384 dbus::ObjectPath(heart_rate_measurement_path_
),
385 FakeBluetoothGattDescriptorClient::
386 kClientCharacteristicConfigurationUUID
));
387 DCHECK(ccc_path
.IsValid());
388 heart_rate_measurement_ccc_desc_path_
= ccc_path
.value();
390 std::vector
<dbus::ObjectPath
> desc_paths
;
391 desc_paths
.push_back(ccc_path
);
393 heart_rate_measurement_properties_
->descriptors
.ReplaceValue(desc_paths
);
396 void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() {
397 VLOG(2) << "Hiding fake Heart Rate characteristics.";
399 // Hide the descriptors.
400 FakeBluetoothGattDescriptorClient
* descriptor_client
=
401 static_cast<FakeBluetoothGattDescriptorClient
*>(
402 DBusThreadManager::Get()->GetBluetoothGattDescriptorClient());
403 descriptor_client
->HideDescriptor(
404 dbus::ObjectPath(heart_rate_measurement_ccc_desc_path_
));
406 // Notify the observers before deleting the properties structures so that they
407 // can be accessed from the observer method.
408 NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_measurement_path_
));
409 NotifyCharacteristicRemoved(dbus::ObjectPath(body_sensor_location_path_
));
410 NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_control_point_path_
));
412 heart_rate_measurement_properties_
.reset();
413 body_sensor_location_properties_
.reset();
414 heart_rate_control_point_properties_
.reset();
416 heart_rate_measurement_path_
.clear();
417 body_sensor_location_path_
.clear();
418 heart_rate_control_point_path_
.clear();
419 heart_rate_visible_
= false;
422 void FakeBluetoothGattCharacteristicClient::SetExtraProcessing(
424 extra_requests_
= requests
;
425 if (extra_requests_
== 0) {
426 for (const auto& it
: action_extra_requests_
) {
427 it
.second
->callback_
.Run();
430 action_extra_requests_
.clear();
433 VLOG(2) << "Requests SLOW now, " << requests
<< " InProgress errors each.";
436 size_t FakeBluetoothGattCharacteristicClient::GetExtraProcessing() const {
437 return extra_requests_
;
441 FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementPath() const {
442 return dbus::ObjectPath(heart_rate_measurement_path_
);
446 FakeBluetoothGattCharacteristicClient::GetBodySensorLocationPath() const {
447 return dbus::ObjectPath(body_sensor_location_path_
);
451 FakeBluetoothGattCharacteristicClient::GetHeartRateControlPointPath() const {
452 return dbus::ObjectPath(heart_rate_control_point_path_
);
455 void FakeBluetoothGattCharacteristicClient::OnPropertyChanged(
456 const dbus::ObjectPath
& object_path
,
457 const std::string
& property_name
) {
458 VLOG(2) << "Characteristic property changed: " << object_path
.value()
459 << ": " << property_name
;
461 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
462 GattCharacteristicPropertyChanged(
463 object_path
, property_name
));
466 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded(
467 const dbus::ObjectPath
& object_path
) {
468 VLOG(2) << "GATT characteristic added: " << object_path
.value();
469 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
470 GattCharacteristicAdded(object_path
));
473 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved(
474 const dbus::ObjectPath
& object_path
) {
475 VLOG(2) << "GATT characteristic removed: " << object_path
.value();
476 FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer
, observers_
,
477 GattCharacteristicRemoved(object_path
));
480 void FakeBluetoothGattCharacteristicClient::
481 ScheduleHeartRateMeasurementValueChange() {
482 if (!IsHeartRateVisible())
485 // Don't send updates if the characteristic is not notifying.
486 if (!heart_rate_measurement_properties_
->notifying
.value())
489 VLOG(2) << "Updating heart rate value.";
490 std::vector
<uint8
> measurement
= GetHeartRateMeasurementValue();
493 BluetoothGattCharacteristicClient::Observer
,
495 GattCharacteristicValueUpdated(
496 dbus::ObjectPath(heart_rate_measurement_path_
), measurement
));
498 base::MessageLoop::current()->PostDelayedTask(
500 base::Bind(&FakeBluetoothGattCharacteristicClient::
501 ScheduleHeartRateMeasurementValueChange
,
502 weak_ptr_factory_
.GetWeakPtr()),
503 base::TimeDelta::FromMilliseconds(
504 kHeartRateMeasurementNotificationIntervalMs
));
508 FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementValue() {
509 // TODO(armansito): We should make sure to properly pack this struct to ensure
510 // correct byte alignment and endianness. It doesn't matter too much right now
511 // as this is a fake and GCC on Linux seems to do the right thing.
515 uint16 energy_expanded
;
519 // Flags in LSB: 0 11 1 1 000
521 // 8-bit bpm format -- | | | |
522 // Sensor contact supported -- | | |
523 // Energy expanded field present -- | |
524 // RR-Interval values present ------- |
525 // Reserved for future use ------------
527 value
.flags
|= (0x03 << 1);
528 value
.flags
|= (0x01 << 3);
529 value
.flags
|= (0x01 << 4);
531 // Pick a value between 117 bpm and 153 bpm for heart rate.
532 value
.bpm
= static_cast<uint8
>(base::RandInt(117, 153));
534 // Total calories burned in kJoules since the last reset. Increment this by 1
535 // every time. It's fine if it overflows: it becomes 0 when the user resets
536 // the heart rate monitor (or pretend that he had a lot of cheeseburgers).
537 value
.energy_expanded
= calories_burned_
++;
539 // Include one RR-Interval value, in seconds.
540 value
.rr_interval
= 60/value
.bpm
;
542 // Return the bytes in an array.
543 uint8
* bytes
= reinterpret_cast<uint8
*>(&value
);
544 std::vector
<uint8
> return_value
;
545 return_value
.assign(bytes
, bytes
+ sizeof(value
));
549 bool FakeBluetoothGattCharacteristicClient::IsHeartRateVisible() const {
550 DCHECK(heart_rate_visible_
!= heart_rate_measurement_path_
.empty());
551 DCHECK(heart_rate_visible_
!= body_sensor_location_path_
.empty());
552 DCHECK(heart_rate_visible_
!= heart_rate_control_point_path_
.empty());
553 DCHECK(heart_rate_visible_
== !!heart_rate_measurement_properties_
.get());
554 DCHECK(heart_rate_visible_
== !!body_sensor_location_properties_
.get());
555 DCHECK(heart_rate_visible_
== !!heart_rate_control_point_properties_
.get());
556 return heart_rate_visible_
;
559 } // namespace chromeos