| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h" |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/rand_util.h" |
| #include "base/time/time.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| const int kStartNotifyResponseIntervalMs = 200; |
| const int kHeartRateMeasurementNotificationIntervalMs = 2000; |
| |
| } // namespace |
| |
| // static |
| const char FakeBluetoothGattCharacteristicClient:: |
| kHeartRateMeasurementPathComponent[] = "char0000"; |
| const char FakeBluetoothGattCharacteristicClient:: |
| kBodySensorLocationPathComponent[] = "char0001"; |
| const char FakeBluetoothGattCharacteristicClient:: |
| kHeartRateControlPointPathComponent[] = "char0002"; |
| |
| // static |
| const char FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID[] = |
| "00002a37-0000-1000-8000-00805f9b34fb"; |
| const char FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID[] = |
| "00002a38-0000-1000-8000-00805f9b34fb"; |
| const char FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID[] = |
| "00002a39-0000-1000-8000-00805f9b34fb"; |
| |
| FakeBluetoothGattCharacteristicClient::Properties::Properties( |
| const PropertyChangedCallback& callback) |
| : BluetoothGattCharacteristicClient::Properties( |
| NULL, |
| bluetooth_gatt_characteristic::kBluetoothGattCharacteristicInterface, |
| callback) { |
| } |
| |
| FakeBluetoothGattCharacteristicClient::Properties::~Properties() { |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::Properties::Get( |
| dbus::PropertyBase* property, |
| dbus::PropertySet::GetCallback callback) { |
| VLOG(1) << "Get " << property->name(); |
| callback.Run(true); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::Properties::GetAll() { |
| VLOG(1) << "GetAll"; |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::Properties::Set( |
| dbus::PropertyBase* property, |
| dbus::PropertySet::SetCallback callback) { |
| VLOG(1) << "Set " << property->name(); |
| callback.Run(false); |
| } |
| |
| FakeBluetoothGattCharacteristicClient::FakeBluetoothGattCharacteristicClient() |
| : heart_rate_visible_(false), |
| calories_burned_(0), |
| weak_ptr_factory_(this) { |
| } |
| |
| FakeBluetoothGattCharacteristicClient:: |
| ~FakeBluetoothGattCharacteristicClient() { |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus* bus) { |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| std::vector<dbus::ObjectPath> |
| FakeBluetoothGattCharacteristicClient::GetCharacteristics() { |
| std::vector<dbus::ObjectPath> paths; |
| if (IsHeartRateVisible()) { |
| paths.push_back(dbus::ObjectPath(heart_rate_measurement_path_)); |
| paths.push_back(dbus::ObjectPath(body_sensor_location_path_)); |
| paths.push_back(dbus::ObjectPath(heart_rate_control_point_path_)); |
| } |
| return paths; |
| } |
| |
| FakeBluetoothGattCharacteristicClient::Properties* |
| FakeBluetoothGattCharacteristicClient::GetProperties( |
| const dbus::ObjectPath& object_path) { |
| if (object_path.value() == heart_rate_measurement_path_) { |
| DCHECK(heart_rate_measurement_properties_.get()); |
| return heart_rate_measurement_properties_.get(); |
| } |
| if (object_path.value() == body_sensor_location_path_) { |
| DCHECK(body_sensor_location_properties_.get()); |
| return body_sensor_location_properties_.get(); |
| } |
| if (object_path.value() == heart_rate_control_point_path_) { |
| DCHECK(heart_rate_control_point_properties_.get()); |
| return heart_rate_control_point_properties_.get(); |
| } |
| return NULL; |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::ReadValue( |
| const dbus::ObjectPath& object_path, |
| const ValueCallback& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsHeartRateVisible()) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| if (object_path.value() == heart_rate_measurement_path_ || |
| object_path.value() == heart_rate_control_point_path_) { |
| error_callback.Run("org.bluez.Error.ReadNotPermitted", |
| "Reads of this value are not allowed"); |
| return; |
| } |
| |
| if (object_path.value() != body_sensor_location_path_) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| std::vector<uint8> value; |
| value.push_back(0x06); // Location is "foot". |
| callback.Run(value); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::WriteValue( |
| const dbus::ObjectPath& object_path, |
| const std::vector<uint8>& value, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsHeartRateVisible()) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| if (object_path.value() != heart_rate_control_point_path_) { |
| error_callback.Run("org.bluez.Error.WriteNotPermitted", |
| "Writes of this value are not allowed"); |
| return; |
| } |
| |
| DCHECK(heart_rate_control_point_properties_.get()); |
| if (value.size() != 1 || value[0] > 1) { |
| error_callback.Run("org.bluez.Error.Failed", |
| "Invalid value given for write"); |
| return; |
| } |
| |
| if (value[0] == 1) |
| calories_burned_ = 0; |
| |
| callback.Run(); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::StartNotify( |
| const dbus::ObjectPath& object_path, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsHeartRateVisible()) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| if (object_path.value() != heart_rate_measurement_path_) { |
| error_callback.Run("org.bluez.Error.NotSupported", |
| "This characteristic does not support notifications"); |
| return; |
| } |
| |
| if (heart_rate_measurement_properties_->notifying.value()) { |
| error_callback.Run("org.bluez.Error.Busy", |
| "Characteristic already notifying"); |
| return; |
| } |
| |
| heart_rate_measurement_properties_->notifying.ReplaceValue(true); |
| ScheduleHeartRateMeasurementValueChange(); |
| |
| // Respond asynchronously. |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| callback, |
| base::TimeDelta::FromMilliseconds(kStartNotifyResponseIntervalMs)); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::StopNotify( |
| const dbus::ObjectPath& object_path, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsHeartRateVisible()) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| if (object_path.value() != heart_rate_measurement_path_) { |
| error_callback.Run("org.bluez.Error.NotSupported", |
| "This characteristic does not support notifications"); |
| return; |
| } |
| |
| if (!heart_rate_measurement_properties_->notifying.value()) { |
| error_callback.Run("org.bluez.Error.Failed", "Not notifying"); |
| return; |
| } |
| |
| heart_rate_measurement_properties_->notifying.ReplaceValue(false); |
| |
| callback.Run(); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::ExposeHeartRateCharacteristics( |
| const dbus::ObjectPath& service_path) { |
| if (IsHeartRateVisible()) { |
| VLOG(2) << "Fake Heart Rate characteristics are already visible."; |
| return; |
| } |
| |
| VLOG(2) << "Exposing fake Heart Rate characteristics."; |
| |
| std::vector<std::string> flags; |
| |
| // ==== Heart Rate Measurement Characteristic ==== |
| heart_rate_measurement_path_ = |
| service_path.value() + "/" + kHeartRateMeasurementPathComponent; |
| heart_rate_measurement_properties_.reset(new Properties(base::Bind( |
| &FakeBluetoothGattCharacteristicClient::OnPropertyChanged, |
| weak_ptr_factory_.GetWeakPtr(), |
| dbus::ObjectPath(heart_rate_measurement_path_)))); |
| heart_rate_measurement_properties_->uuid.ReplaceValue( |
| kHeartRateMeasurementUUID); |
| heart_rate_measurement_properties_->service.ReplaceValue(service_path); |
| flags.push_back(bluetooth_gatt_characteristic::kFlagNotify); |
| heart_rate_measurement_properties_->flags.ReplaceValue(flags); |
| |
| // ==== Body Sensor Location Characteristic ==== |
| body_sensor_location_path_ = |
| service_path.value() + "/" + kBodySensorLocationPathComponent; |
| body_sensor_location_properties_.reset(new Properties(base::Bind( |
| &FakeBluetoothGattCharacteristicClient::OnPropertyChanged, |
| weak_ptr_factory_.GetWeakPtr(), |
| dbus::ObjectPath(body_sensor_location_path_)))); |
| body_sensor_location_properties_->uuid.ReplaceValue(kBodySensorLocationUUID); |
| body_sensor_location_properties_->service.ReplaceValue(service_path); |
| flags.clear(); |
| flags.push_back(bluetooth_gatt_characteristic::kFlagRead); |
| body_sensor_location_properties_->flags.ReplaceValue(flags); |
| |
| // ==== Heart Rate Control Point Characteristic ==== |
| heart_rate_control_point_path_ = |
| service_path.value() + "/" + kHeartRateControlPointPathComponent; |
| heart_rate_control_point_properties_.reset(new Properties(base::Bind( |
| &FakeBluetoothGattCharacteristicClient::OnPropertyChanged, |
| weak_ptr_factory_.GetWeakPtr(), |
| dbus::ObjectPath(heart_rate_control_point_path_)))); |
| heart_rate_control_point_properties_->uuid.ReplaceValue( |
| kHeartRateControlPointUUID); |
| heart_rate_control_point_properties_->service.ReplaceValue(service_path); |
| flags.clear(); |
| flags.push_back(bluetooth_gatt_characteristic::kFlagWrite); |
| heart_rate_control_point_properties_->flags.ReplaceValue(flags); |
| |
| heart_rate_visible_ = true; |
| |
| NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_measurement_path_)); |
| NotifyCharacteristicAdded(dbus::ObjectPath(body_sensor_location_path_)); |
| NotifyCharacteristicAdded(dbus::ObjectPath(heart_rate_control_point_path_)); |
| |
| // Expose CCC descriptor for Heart Rate Measurement characteristic. |
| FakeBluetoothGattDescriptorClient* descriptor_client = |
| static_cast<FakeBluetoothGattDescriptorClient*>( |
| DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()); |
| dbus::ObjectPath ccc_path(descriptor_client->ExposeDescriptor( |
| dbus::ObjectPath(heart_rate_measurement_path_), |
| FakeBluetoothGattDescriptorClient:: |
| kClientCharacteristicConfigurationUUID)); |
| DCHECK(ccc_path.IsValid()); |
| heart_rate_measurement_ccc_desc_path_ = ccc_path.value(); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() { |
| VLOG(2) << "Hiding fake Heart Rate characteristics."; |
| |
| // Hide the descriptors. |
| FakeBluetoothGattDescriptorClient* descriptor_client = |
| static_cast<FakeBluetoothGattDescriptorClient*>( |
| DBusThreadManager::Get()->GetBluetoothGattDescriptorClient()); |
| descriptor_client->HideDescriptor( |
| dbus::ObjectPath(heart_rate_measurement_ccc_desc_path_)); |
| |
| // Notify the observers before deleting the properties structures so that they |
| // can be accessed from the observer method. |
| NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_measurement_path_)); |
| NotifyCharacteristicRemoved(dbus::ObjectPath(body_sensor_location_path_)); |
| NotifyCharacteristicRemoved(dbus::ObjectPath(heart_rate_control_point_path_)); |
| |
| heart_rate_measurement_properties_.reset(); |
| body_sensor_location_properties_.reset(); |
| heart_rate_control_point_properties_.reset(); |
| |
| heart_rate_measurement_path_.clear(); |
| body_sensor_location_path_.clear(); |
| heart_rate_control_point_path_.clear(); |
| heart_rate_visible_ = false; |
| } |
| |
| dbus::ObjectPath |
| FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementPath() const { |
| return dbus::ObjectPath(heart_rate_measurement_path_); |
| } |
| |
| dbus::ObjectPath |
| FakeBluetoothGattCharacteristicClient::GetBodySensorLocationPath() const { |
| return dbus::ObjectPath(body_sensor_location_path_); |
| } |
| |
| dbus::ObjectPath |
| FakeBluetoothGattCharacteristicClient::GetHeartRateControlPointPath() const { |
| return dbus::ObjectPath(heart_rate_control_point_path_); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::OnPropertyChanged( |
| const dbus::ObjectPath& object_path, |
| const std::string& property_name) { |
| VLOG(2) << "Characteristic property changed: " << object_path.value() |
| << ": " << property_name; |
| |
| FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_, |
| GattCharacteristicPropertyChanged( |
| object_path, property_name)); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded( |
| const dbus::ObjectPath& object_path) { |
| VLOG(2) << "GATT characteristic added: " << object_path.value(); |
| FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_, |
| GattCharacteristicAdded(object_path)); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved( |
| const dbus::ObjectPath& object_path) { |
| VLOG(2) << "GATT characteristic removed: " << object_path.value(); |
| FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_, |
| GattCharacteristicRemoved(object_path)); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient:: |
| ScheduleHeartRateMeasurementValueChange() { |
| if (!IsHeartRateVisible()) |
| return; |
| |
| // Don't send updates if the characteristic is not notifying. |
| if (!heart_rate_measurement_properties_->notifying.value()) |
| return; |
| |
| VLOG(2) << "Updating heart rate value."; |
| std::vector<uint8> measurement = GetHeartRateMeasurementValue(); |
| |
| FOR_EACH_OBSERVER( |
| BluetoothGattCharacteristicClient::Observer, |
| observers_, |
| GattCharacteristicValueUpdated( |
| dbus::ObjectPath(heart_rate_measurement_path_), measurement)); |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&FakeBluetoothGattCharacteristicClient:: |
| ScheduleHeartRateMeasurementValueChange, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds( |
| kHeartRateMeasurementNotificationIntervalMs)); |
| } |
| |
| std::vector<uint8> |
| FakeBluetoothGattCharacteristicClient::GetHeartRateMeasurementValue() { |
| // TODO(armansito): We should make sure to properly pack this struct to ensure |
| // correct byte alignment and endianness. It doesn't matter too much right now |
| // as this is a fake and GCC on Linux seems to do the right thing. |
| struct { |
| uint8 flags; |
| uint8 bpm; |
| uint16 energy_expanded; |
| uint16 rr_interval; |
| } value; |
| |
| // Flags in LSB: 0 11 1 1 000 |
| // | | | | | |
| // 8-bit bpm format -- | | | | |
| // Sensor contact supported -- | | | |
| // Energy expanded field present -- | | |
| // RR-Interval values present ------- | |
| // Reserved for future use ------------ |
| value.flags = 0x0; |
| value.flags |= (0x03 << 1); |
| value.flags |= (0x01 << 3); |
| value.flags |= (0x01 << 4); |
| |
| // Pick a value between 117 bpm and 153 bpm for heart rate. |
| value.bpm = static_cast<uint8>(base::RandInt(117, 153)); |
| |
| // Total calories burned in kJoules since the last reset. Increment this by 1 |
| // every time. It's fine if it overflows: it becomes 0 when the user resets |
| // the heart rate monitor (or pretend that he had a lot of cheeseburgers). |
| value.energy_expanded = calories_burned_++; |
| |
| // Include one RR-Interval value, in seconds. |
| value.rr_interval = 60/value.bpm; |
| |
| // Return the bytes in an array. |
| uint8* bytes = reinterpret_cast<uint8*>(&value); |
| std::vector<uint8> return_value; |
| return_value.assign(bytes, bytes + sizeof(value)); |
| return return_value; |
| } |
| |
| bool FakeBluetoothGattCharacteristicClient::IsHeartRateVisible() const { |
| DCHECK(heart_rate_visible_ != heart_rate_measurement_path_.empty()); |
| DCHECK(heart_rate_visible_ != body_sensor_location_path_.empty()); |
| DCHECK(heart_rate_visible_ != heart_rate_control_point_path_.empty()); |
| DCHECK(heart_rate_visible_ == !!heart_rate_measurement_properties_.get()); |
| DCHECK(heart_rate_visible_ == !!body_sensor_location_properties_.get()); |
| DCHECK(heart_rate_visible_ == !!heart_rate_control_point_properties_.get()); |
| return heart_rate_visible_; |
| } |
| |
| } // namespace chromeos |