| // 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/threading/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() = |
| default; |
| |
| // 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() = default; |
| |
| 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<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 (!authenticated_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotPaired, "Please login"); |
| return; |
| } |
| |
| if (!authorized_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotAuthorized, |
| "Authorize first"); |
| return; |
| } |
| |
| if (object_path.value() == heart_rate_control_point_path_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorReadNotPermitted, |
| "Reads of this value are not allowed"); |
| return; |
| } |
| |
| if (object_path.value() == heart_rate_measurement_path_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotSupported, |
| "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(bluetooth_gatt_service::kErrorInProgress, |
| "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<uint8_t> 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<uint8_t>& value, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (!authenticated_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotPaired, "Please login"); |
| return; |
| } |
| |
| if (!authorized_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotAuthorized, |
| "Authorize first"); |
| return; |
| } |
| |
| if (!IsHeartRateVisible()) { |
| error_callback.Run(kUnknownCharacteristicError, ""); |
| return; |
| } |
| |
| if (object_path.value() == heart_rate_measurement_path_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorNotSupported, |
| "Action not supported on this characteristic"); |
| return; |
| } |
| |
| if (object_path.value() != heart_rate_control_point_path_) { |
| error_callback.Run(bluetooth_gatt_service::kErrorWriteNotPermitted, |
| "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(bluetooth_gatt_service::kErrorInProgress, |
| "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, bluetooth_gatt_service::kErrorInvalidValueLength, |
| "Invalid length for write"); |
| } else if (value[0] > 1) { |
| completed_callback = |
| base::Bind(error_callback, bluetooth_gatt_service::kErrorFailed, |
| "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(bluetooth_gatt_service::kErrorNotSupported, |
| "This characteristic does not support notifications"); |
| return; |
| } |
| |
| if (heart_rate_measurement_properties_->notifying.value()) { |
| error_callback.Run(bluetooth_gatt_service::kErrorInProgress, |
| "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(bluetooth_gatt_service::kErrorNotSupported, |
| "This characteristic does not support notifications"); |
| return; |
| } |
| |
| if (!heart_rate_measurement_properties_->notifying.value()) { |
| error_callback.Run(bluetooth_gatt_service::kErrorFailed, "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*>( |
| 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(); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::HideHeartRateCharacteristics() { |
| VLOG(2) << "Hiding fake Heart Rate characteristics."; |
| |
| // Hide the descriptors. |
| FakeBluetoothGattDescriptorClient* descriptor_client = |
| static_cast<FakeBluetoothGattDescriptorClient*>( |
| 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 (auto& observer : observers_) |
| observer.GattCharacteristicPropertyChanged(object_path, property_name); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded( |
| const dbus::ObjectPath& object_path) { |
| VLOG(2) << "GATT characteristic added: " << object_path.value(); |
| for (auto& observer : observers_) |
| observer.GattCharacteristicAdded(object_path); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved( |
| const dbus::ObjectPath& object_path) { |
| VLOG(2) << "GATT characteristic removed: " << object_path.value(); |
| for (auto& observer : observers_) |
| observer.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_t> measurement = GetHeartRateMeasurementValue(); |
| heart_rate_measurement_properties_->value.ReplaceValue(measurement); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&FakeBluetoothGattCharacteristicClient:: |
| ScheduleHeartRateMeasurementValueChange, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds( |
| kHeartRateMeasurementNotificationIntervalMs)); |
| } |
| |
| void FakeBluetoothGattCharacteristicClient::DelayedReadValueCallback( |
| const dbus::ObjectPath& object_path, |
| const ValueCallback& callback, |
| const std::vector<uint8_t>& value) { |
| Properties* properties = GetProperties(object_path); |
| DCHECK(properties); |
| |
| properties->value.ReplaceValue(value); |
| callback.Run(value); |
| } |
| |
| std::vector<uint8_t> |
| 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<uint8_t>(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 they 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<uint8_t*>(&value); |
| std::vector<uint8_t> 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 |