// 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 "device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h" #include "base/bind.h" #include "base/location.h" #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace bluez { namespace { const int kStartNotifyResponseIntervalMs = 200; const int kHeartRateMeasurementNotificationIntervalMs = 2000; } // namespace FakeBluetoothGattCharacteristicClient::DelayedCallback::DelayedCallback( base::Closure callback, size_t delay) : callback_(callback), delay_(delay) {} FakeBluetoothGattCharacteristicClient::DelayedCallback::~DelayedCallback() {} // 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), authorized_(true), authenticated_(true), calories_burned_(0), extra_requests_(0), weak_ptr_factory_(this) {} FakeBluetoothGattCharacteristicClient:: ~FakeBluetoothGattCharacteristicClient() { for (const auto& it : action_extra_requests_) { delete it.second; } action_extra_requests_.clear(); } void FakeBluetoothGattCharacteristicClient::Init(dbus::Bus* bus) {} void FakeBluetoothGattCharacteristicClient::AddObserver(Observer* observer) { observers_.AddObserver(observer); } void FakeBluetoothGattCharacteristicClient::RemoveObserver(Observer* observer) { observers_.RemoveObserver(observer); } std::vector FakeBluetoothGattCharacteristicClient::GetCharacteristics() { std::vector 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 (!authenticated_) { error_callback.Run("org.bluez.Error.NotPaired", "Please login"); return; } if (!authorized_) { error_callback.Run("org.bluez.Error.NotAuthorized", "Authorize first"); return; } if (object_path.value() == heart_rate_control_point_path_) { error_callback.Run("org.bluez.Error.NotPermitted", "Reads of this value are not allowed"); return; } if (object_path.value() == heart_rate_measurement_path_) { error_callback.Run("org.bluez.Error.NotSupported", "Action not supported on this characteristic"); return; } if (object_path.value() != body_sensor_location_path_) { error_callback.Run(kUnknownCharacteristicError, ""); return; } if (action_extra_requests_.find("ReadValue") != action_extra_requests_.end()) { DelayedCallback* delayed = action_extra_requests_["ReadValue"]; delayed->delay_--; error_callback.Run("org.bluez.Error.InProgress", "Another read is currenty in progress"); if (delayed->delay_ == 0) { delayed->callback_.Run(); action_extra_requests_.erase("ReadValue"); delete delayed; } return; } base::Closure completed_callback; if (!IsHeartRateVisible()) { completed_callback = base::Bind(error_callback, kUnknownCharacteristicError, ""); } else { std::vector value = {0x06}; // Location is "foot". completed_callback = base::Bind( &FakeBluetoothGattCharacteristicClient::DelayedReadValueCallback, weak_ptr_factory_.GetWeakPtr(), object_path, callback, value); } if (extra_requests_ > 0) { action_extra_requests_["ReadValue"] = new DelayedCallback(completed_callback, extra_requests_); return; } completed_callback.Run(); } void FakeBluetoothGattCharacteristicClient::WriteValue( const dbus::ObjectPath& object_path, const std::vector& value, const base::Closure& callback, const ErrorCallback& error_callback) { if (!authenticated_) { error_callback.Run("org.bluez.Error.NotPaired", "Please login"); return; } if (!authorized_) { error_callback.Run("org.bluez.Error.NotAuthorized", "Authorize first"); return; } if (!IsHeartRateVisible()) { error_callback.Run(kUnknownCharacteristicError, ""); return; } if (object_path.value() == heart_rate_measurement_path_) { error_callback.Run("org.bluez.Error.NotSupported", "Action not supported on this characteristic"); return; } if (object_path.value() != heart_rate_control_point_path_) { error_callback.Run("org.bluez.Error.NotPermitted", "Writes of this value are not allowed"); return; } DCHECK(heart_rate_control_point_properties_.get()); if (action_extra_requests_.find("WriteValue") != action_extra_requests_.end()) { DelayedCallback* delayed = action_extra_requests_["WriteValue"]; delayed->delay_--; error_callback.Run("org.bluez.Error.InProgress", "Another write is in progress"); if (delayed->delay_ == 0) { delayed->callback_.Run(); action_extra_requests_.erase("WriteValue"); delete delayed; } return; } base::Closure completed_callback; if (value.size() != 1) { completed_callback = base::Bind(error_callback, "org.bluez.Error.InvalidValueLength", "Invalid length for write"); } else if (value[0] > 1) { completed_callback = base::Bind(error_callback, "org.bluez.Error.Failed", "Invalid value given for write"); } else if (value[0] == 1) { // TODO(jamuraa): make this happen when the callback happens calories_burned_ = 0; completed_callback = callback; } if (extra_requests_ > 0) { action_extra_requests_["WriteValue"] = new DelayedCallback(completed_callback, extra_requests_); return; } completed_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.InProgress", "Characteristic already notifying"); return; } heart_rate_measurement_properties_->notifying.ReplaceValue(true); ScheduleHeartRateMeasurementValueChange(); // Respond asynchronously. base::ThreadTaskRunnerHandle::Get()->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 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( bluez::BluezDBusManager::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(); std::vector desc_paths; desc_paths.push_back(ccc_path); heart_rate_measurement_properties_->descriptors.ReplaceValue(desc_paths); } void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() { VLOG(2) << "Hiding fake Heart Rate characteristics."; // Hide the descriptors. FakeBluetoothGattDescriptorClient* descriptor_client = static_cast( bluez::BluezDBusManager::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; } void FakeBluetoothGattCharacteristicClient::SetExtraProcessing( size_t requests) { extra_requests_ = requests; if (extra_requests_ == 0) { for (const auto& it : action_extra_requests_) { it.second->callback_.Run(); delete it.second; } action_extra_requests_.clear(); return; } VLOG(2) << "Requests SLOW now, " << requests << " InProgress errors each."; } size_t FakeBluetoothGattCharacteristicClient::GetExtraProcessing() const { return extra_requests_; } 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 measurement = GetHeartRateMeasurementValue(); heart_rate_measurement_properties_->value.ReplaceValue(measurement); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&FakeBluetoothGattCharacteristicClient:: ScheduleHeartRateMeasurementValueChange, weak_ptr_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds( kHeartRateMeasurementNotificationIntervalMs)); } void FakeBluetoothGattCharacteristicClient::DelayedReadValueCallback( const dbus::ObjectPath& object_path, const ValueCallback& callback, const std::vector& value) { Properties* properties = GetProperties(object_path); DCHECK(properties); properties->value.ReplaceValue(value); callback.Run(value); } std::vector 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_t flags; uint8_t bpm; uint16_t energy_expanded; uint16_t 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(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_t* bytes = reinterpret_cast(&value); std::vector 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 bluez