| // 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/bluetooth_low_energy_win.h" |
| |
| #include "base/files/file.h" |
| #include "base/logging.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/windows_version.h" |
| |
| namespace { |
| |
| using device::win::DeviceRegistryPropertyValue; |
| using device::win::DevicePropertyValue; |
| using device::win::BluetoothLowEnergyDeviceInfo; |
| using device::win::BluetoothLowEnergyServiceInfo; |
| |
| const char kPlatformNotSupported[] = |
| "Bluetooth Low energy is only supported on Windows 8 and later."; |
| const char kDeviceEnumError[] = "Error enumerating Bluetooth LE devices."; |
| const char kDeviceInfoError[] = |
| "Error retrieving Bluetooth LE device information."; |
| const char kDeviceAddressError[] = |
| "Device instance ID value does not seem to contain a Bluetooth Adapter " |
| "address."; |
| const char kDeviceFriendlyNameError[] = "Device name is not valid."; |
| const char kInvalidBluetoothAddress[] = "Bluetooth address format is invalid."; |
| |
| // Like ScopedHandle but for HDEVINFO. Only use this on HDEVINFO returned from |
| // SetupDiGetClassDevs. |
| class DeviceInfoSetTraits { |
| public: |
| typedef HDEVINFO Handle; |
| |
| static bool CloseHandle(HDEVINFO handle) { |
| return ::SetupDiDestroyDeviceInfoList(handle) != FALSE; |
| } |
| |
| static bool IsHandleValid(HDEVINFO handle) { |
| return handle != INVALID_HANDLE_VALUE; |
| } |
| |
| static HDEVINFO NullHandle() { return INVALID_HANDLE_VALUE; } |
| |
| private: |
| DISALLOW_IMPLICIT_CONSTRUCTORS(DeviceInfoSetTraits); |
| }; |
| |
| typedef base::win::GenericScopedHandle<DeviceInfoSetTraits, |
| base::win::VerifierTraits> |
| ScopedDeviceInfoSetHandle; |
| |
| bool StringToBluetoothAddress(const std::string& value, |
| BLUETOOTH_ADDRESS* btha, |
| std::string* error) { |
| if (value.length() != 6 * 2) { |
| *error = kInvalidBluetoothAddress; |
| return false; |
| } |
| |
| int buffer[6]; |
| int result = sscanf_s(value.c_str(), |
| "%02X%02X%02X%02X%02X%02X", |
| &buffer[5], |
| &buffer[4], |
| &buffer[3], |
| &buffer[2], |
| &buffer[1], |
| &buffer[0]); |
| if (result != 6) { |
| *error = kInvalidBluetoothAddress; |
| return false; |
| } |
| |
| ZeroMemory(btha, sizeof(*btha)); |
| btha->rgBytes[0] = buffer[0]; |
| btha->rgBytes[1] = buffer[1]; |
| btha->rgBytes[2] = buffer[2]; |
| btha->rgBytes[3] = buffer[3]; |
| btha->rgBytes[4] = buffer[4]; |
| btha->rgBytes[5] = buffer[5]; |
| return true; |
| } |
| |
| std::string FormatBluetoothError(const char* message, HRESULT hr) { |
| std::ostringstream string_stream; |
| string_stream << message; |
| if (FAILED(hr)) |
| string_stream << logging::SystemErrorCodeToString(hr); |
| return string_stream.str(); |
| } |
| |
| bool CheckInsufficientBuffer(bool success, |
| const char* message, |
| std::string* error) { |
| if (success) { |
| *error = FormatBluetoothError(message, S_OK); |
| return false; |
| } |
| |
| HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); |
| if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { |
| *error = FormatBluetoothError(message, hr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckHResult(HRESULT hr, const char* message, std::string* error) { |
| if (FAILED(hr)) { |
| *error = FormatBluetoothError(message, hr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckSuccess(bool success, const char* message, std::string* error) { |
| if (!success) { |
| CheckHResult(HRESULT_FROM_WIN32(GetLastError()), message, error); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckNoData(HRESULT hr, size_t length) { |
| if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) |
| return true; |
| |
| if (SUCCEEDED(hr) && length == 0) |
| return true; |
| |
| return false; |
| } |
| |
| bool CheckMoreData(HRESULT hr, const char* message, std::string* error) { |
| if (SUCCEEDED(hr)) { |
| *error = FormatBluetoothError(message, hr); |
| return false; |
| } |
| |
| if (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { |
| *error = FormatBluetoothError(message, hr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckExpectedLength(size_t actual_length, |
| size_t expected_length, |
| const char* message, |
| std::string* error) { |
| if (actual_length != expected_length) { |
| *error = FormatBluetoothError(message, E_FAIL); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceProperty( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| const DEVPROPKEY& key, |
| scoped_ptr<DevicePropertyValue>* value, |
| std::string* error) { |
| DWORD required_length; |
| DEVPROPTYPE prop_type; |
| BOOL success = SetupDiGetDeviceProperty(device_info_handle.Get(), |
| device_info_data, |
| &key, |
| &prop_type, |
| NULL, |
| 0, |
| &required_length, |
| 0); |
| if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error)) |
| return false; |
| |
| scoped_ptr<uint8_t[]> prop_value(new uint8_t[required_length]); |
| DWORD actual_length = required_length; |
| success = SetupDiGetDeviceProperty(device_info_handle.Get(), |
| device_info_data, |
| &key, |
| &prop_type, |
| prop_value.get(), |
| actual_length, |
| &required_length, |
| 0); |
| if (!CheckSuccess(!!success, kDeviceInfoError, error)) |
| return false; |
| if (!CheckExpectedLength( |
| actual_length, required_length, kDeviceInfoError, error)) { |
| return false; |
| } |
| |
| (*value) = scoped_ptr<DevicePropertyValue>( |
| new DevicePropertyValue(prop_type, prop_value.Pass(), actual_length)); |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceRegistryProperty( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| DWORD property_id, |
| scoped_ptr<DeviceRegistryPropertyValue>* value, |
| std::string* error) { |
| ULONG required_length = 0; |
| BOOL success = SetupDiGetDeviceRegistryProperty(device_info_handle.Get(), |
| device_info_data, |
| property_id, |
| NULL, |
| NULL, |
| 0, |
| &required_length); |
| if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error)) |
| return false; |
| |
| scoped_ptr<uint8_t[]> property_value(new uint8_t[required_length]); |
| ULONG actual_length = required_length; |
| DWORD property_type; |
| success = SetupDiGetDeviceRegistryProperty(device_info_handle.Get(), |
| device_info_data, |
| property_id, |
| &property_type, |
| property_value.get(), |
| actual_length, |
| &required_length); |
| if (!CheckSuccess(!!success, kDeviceInfoError, error)) |
| return false; |
| if (!CheckExpectedLength( |
| actual_length, required_length, kDeviceInfoError, error)) { |
| return false; |
| } |
| |
| (*value) = DeviceRegistryPropertyValue::Create( |
| property_type, property_value.Pass(), actual_length).Pass(); |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceInstanceId( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info, |
| std::string* error) { |
| ULONG required_length = 0; |
| BOOL success = SetupDiGetDeviceInstanceId( |
| device_info_handle.Get(), device_info_data, NULL, 0, &required_length); |
| if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error)) |
| return false; |
| |
| scoped_ptr<WCHAR[]> instance_id(new WCHAR[required_length]); |
| ULONG actual_length = required_length; |
| success = SetupDiGetDeviceInstanceId(device_info_handle.Get(), |
| device_info_data, |
| instance_id.get(), |
| actual_length, |
| &required_length); |
| if (!CheckSuccess(!!success, kDeviceInfoError, error)) |
| return false; |
| if (!CheckExpectedLength( |
| actual_length, required_length, kDeviceInfoError, error)) { |
| return false; |
| } |
| |
| if (actual_length >= 1) { |
| // Ensure string is zero terminated. |
| instance_id.get()[actual_length - 1] = 0; |
| device_info->id = base::SysWideToUTF8(instance_id.get()); |
| } |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceFriendlyName( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info, |
| std::string* error) { |
| scoped_ptr<DeviceRegistryPropertyValue> property_value; |
| if (!CollectBluetoothLowEnergyDeviceRegistryProperty(device_info_handle, |
| device_info_data, |
| SPDRP_FRIENDLYNAME, |
| &property_value, |
| error)) { |
| return false; |
| } |
| |
| if (property_value->property_type() != REG_SZ) { |
| *error = kDeviceFriendlyNameError; |
| return false; |
| } |
| |
| device_info->friendly_name = property_value->AsString(); |
| return true; |
| } |
| |
| bool ExtractBluetoothAddressFromDeviceInstanceId(const std::string& instance_id, |
| BLUETOOTH_ADDRESS* btha, |
| std::string* error) { |
| size_t start = instance_id.find("_"); |
| if (start == std::string::npos) { |
| *error = kDeviceAddressError; |
| return false; |
| } |
| size_t end = instance_id.find("\\", start); |
| if (end == std::string::npos) { |
| *error = kDeviceAddressError; |
| return false; |
| } |
| |
| start++; |
| std::string address = instance_id.substr(start, end - start); |
| if (!StringToBluetoothAddress(address, btha, error)) |
| return false; |
| |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceAddress( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info, |
| std::string* error) { |
| // TODO(rpaquay): We exctract the bluetooth device address from the device |
| // instance ID string, as we did not find a more formal API for retrieving the |
| // bluetooth address of a Bluetooth Low Energy device. |
| // An Bluetooth device instance ID has the following format (under Win8+): |
| // BTHLE\DEV_BC6A29AB5FB0\8&31038925&0&BC6A29AB5FB0 |
| return ExtractBluetoothAddressFromDeviceInstanceId( |
| device_info->id, &device_info->address, error); |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceStatus( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVINFO_DATA device_info_data, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>& device_info, |
| std::string* error) { |
| scoped_ptr<DevicePropertyValue> value; |
| if (!CollectBluetoothLowEnergyDeviceProperty(device_info_handle, |
| device_info_data, |
| DEVPKEY_Device_DevNodeStatus, |
| &value, |
| error)) { |
| return false; |
| } |
| |
| if (value->property_type() != DEVPROP_TYPE_UINT32) { |
| *error = kDeviceInfoError; |
| return false; |
| } |
| |
| device_info->connected = !(value->AsUint32() & DN_DEVICE_DISCONNECTED); |
| // Windows 8 exposes BLE devices only if they are visible and paired. This |
| // might change in the future if Windows offers a public API for discovering |
| // and pairing BLE devices. |
| device_info->visible = true; |
| device_info->authenticated = true; |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceServices( |
| const base::FilePath& device_path, |
| ScopedVector<BluetoothLowEnergyServiceInfo>* services, |
| std::string* error) { |
| base::File file(device_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (!file.IsValid()) { |
| *error = file.ErrorToString(file.error_details()); |
| return false; |
| } |
| |
| USHORT required_length; |
| HRESULT hr = BluetoothGATTGetServices(file.GetPlatformFile(), |
| 0, |
| NULL, |
| &required_length, |
| BLUETOOTH_GATT_FLAG_NONE); |
| if (CheckNoData(hr, required_length)) |
| return true; |
| if (!CheckMoreData(hr, kDeviceInfoError, error)) |
| return false; |
| |
| scoped_ptr<BTH_LE_GATT_SERVICE[]> gatt_services( |
| new BTH_LE_GATT_SERVICE[required_length]); |
| USHORT actual_length = required_length; |
| hr = BluetoothGATTGetServices(file.GetPlatformFile(), |
| actual_length, |
| gatt_services.get(), |
| &required_length, |
| BLUETOOTH_GATT_FLAG_NONE); |
| if (!CheckHResult(hr, kDeviceInfoError, error)) |
| return false; |
| if (!CheckExpectedLength( |
| actual_length, required_length, kDeviceInfoError, error)) { |
| return false; |
| } |
| |
| for (USHORT i = 0; i < actual_length; ++i) { |
| BTH_LE_GATT_SERVICE& gatt_service(gatt_services.get()[i]); |
| BluetoothLowEnergyServiceInfo* service_info = |
| new BluetoothLowEnergyServiceInfo(); |
| service_info->uuid = gatt_service.ServiceUuid; |
| services->push_back(service_info); |
| } |
| |
| return true; |
| } |
| |
| bool CollectBluetoothLowEnergyDeviceInfo( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| PSP_DEVICE_INTERFACE_DATA device_interface_data, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info, |
| std::string* error) { |
| // Retrieve required # of bytes for interface details |
| ULONG required_length = 0; |
| BOOL success = SetupDiGetDeviceInterfaceDetail(device_info_handle.Get(), |
| device_interface_data, |
| NULL, |
| 0, |
| &required_length, |
| NULL); |
| if (!CheckInsufficientBuffer(!!success, kDeviceInfoError, error)) |
| return false; |
| |
| scoped_ptr<uint8_t[]> interface_data(new uint8_t[required_length]); |
| ZeroMemory(interface_data.get(), required_length); |
| |
| PSP_DEVICE_INTERFACE_DETAIL_DATA device_interface_detail_data = |
| reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(interface_data.get()); |
| device_interface_detail_data->cbSize = |
| sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); |
| |
| SP_DEVINFO_DATA device_info_data = {0}; |
| device_info_data.cbSize = sizeof(SP_DEVINFO_DATA); |
| |
| ULONG actual_length = required_length; |
| success = SetupDiGetDeviceInterfaceDetail(device_info_handle.Get(), |
| device_interface_data, |
| device_interface_detail_data, |
| actual_length, |
| &required_length, |
| &device_info_data); |
| if (!CheckSuccess(!!success, kDeviceInfoError, error)) |
| return false; |
| if (!CheckExpectedLength( |
| actual_length, required_length, kDeviceInfoError, error)) { |
| return false; |
| } |
| |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo> result( |
| new device::win::BluetoothLowEnergyDeviceInfo()); |
| result->path = |
| base::FilePath(std::wstring(device_interface_detail_data->DevicePath)); |
| if (!CollectBluetoothLowEnergyDeviceInstanceId( |
| device_info_handle, &device_info_data, result, error)) { |
| return false; |
| } |
| if (!CollectBluetoothLowEnergyDeviceFriendlyName( |
| device_info_handle, &device_info_data, result, error)) { |
| return false; |
| } |
| if (!CollectBluetoothLowEnergyDeviceAddress( |
| device_info_handle, &device_info_data, result, error)) { |
| return false; |
| } |
| if (!CollectBluetoothLowEnergyDeviceStatus( |
| device_info_handle, &device_info_data, result, error)) { |
| return false; |
| } |
| (*device_info) = result.Pass(); |
| return true; |
| } |
| |
| enum DeviceInfoResult { kOk, kError, kNoMoreDevices }; |
| |
| DeviceInfoResult EnumerateSingleBluetoothLowEnergyDevice( |
| const ScopedDeviceInfoSetHandle& device_info_handle, |
| DWORD device_index, |
| scoped_ptr<device::win::BluetoothLowEnergyDeviceInfo>* device_info, |
| std::string* error) { |
| // Enumerate device of BLUETOOTHLE_DEVICE interface class |
| GUID BluetoothInterfaceGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE; |
| SP_DEVICE_INTERFACE_DATA device_interface_data = {0}; |
| device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); |
| BOOL success = ::SetupDiEnumDeviceInterfaces(device_info_handle.Get(), |
| NULL, |
| &BluetoothInterfaceGUID, |
| device_index, |
| &device_interface_data); |
| if (!success) { |
| HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); |
| if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { |
| return kNoMoreDevices; |
| } |
| *error = FormatBluetoothError(kDeviceInfoError, hr); |
| return kError; |
| } |
| |
| if (!CollectBluetoothLowEnergyDeviceInfo( |
| device_info_handle, &device_interface_data, device_info, error)) { |
| return kError; |
| } |
| |
| return kOk; |
| } |
| |
| // Opens a Device Info Set that can be used to enumerate Bluetooth LE devices |
| // present on the machine. |
| HRESULT OpenBluetoothLowEnergyDevices(ScopedDeviceInfoSetHandle* handle) { |
| GUID BluetoothClassGUID = GUID_BLUETOOTHLE_DEVICE_INTERFACE; |
| ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs( |
| &BluetoothClassGUID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); |
| if (!result.IsValid()) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| (*handle) = result.Pass(); |
| return S_OK; |
| } |
| |
| // Opens a Device Info Set that can be used to enumerate Bluetooth LE devices |
| // exposing a service GUID. |
| HRESULT OpenBluetoothLowEnergyService(const GUID& service_guid, |
| ScopedDeviceInfoSetHandle* handle) { |
| ScopedDeviceInfoSetHandle result(SetupDiGetClassDevs( |
| &service_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); |
| if (!result.IsValid()) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| (*handle) = result.Pass(); |
| return S_OK; |
| } |
| |
| } // namespace |
| |
| namespace device { |
| namespace win { |
| |
| // static |
| scoped_ptr<DeviceRegistryPropertyValue> DeviceRegistryPropertyValue::Create( |
| DWORD property_type, |
| scoped_ptr<uint8_t[]> value, |
| size_t value_size) { |
| switch (property_type) { |
| case REG_SZ: { |
| // Ensure string is zero terminated. |
| size_t character_size = value_size / sizeof(WCHAR); |
| CHECK_EQ(character_size * sizeof(WCHAR), value_size); |
| CHECK_GE(character_size, 1u); |
| WCHAR* value_string = reinterpret_cast<WCHAR*>(value.get()); |
| value_string[character_size - 1] = 0; |
| break; |
| } |
| case REG_DWORD: { |
| CHECK_EQ(value_size, sizeof(DWORD)); |
| break; |
| } |
| } |
| return scoped_ptr<DeviceRegistryPropertyValue>( |
| new DeviceRegistryPropertyValue(property_type, value.Pass(), value_size)); |
| } |
| |
| DeviceRegistryPropertyValue::DeviceRegistryPropertyValue( |
| DWORD property_type, |
| scoped_ptr<uint8_t[]> value, |
| size_t value_size) |
| : property_type_(property_type), |
| value_(value.Pass()), |
| value_size_(value_size) { |
| } |
| |
| DeviceRegistryPropertyValue::~DeviceRegistryPropertyValue() { |
| } |
| |
| std::string DeviceRegistryPropertyValue::AsString() const { |
| CHECK_EQ(property_type_, static_cast<DWORD>(REG_SZ)); |
| WCHAR* value_string = reinterpret_cast<WCHAR*>(value_.get()); |
| return base::SysWideToUTF8(value_string); |
| } |
| |
| DWORD DeviceRegistryPropertyValue::AsDWORD() const { |
| CHECK_EQ(property_type_, static_cast<DWORD>(REG_DWORD)); |
| DWORD* value = reinterpret_cast<DWORD*>(value_.get()); |
| return *value; |
| } |
| |
| DevicePropertyValue::DevicePropertyValue(DEVPROPTYPE property_type, |
| scoped_ptr<uint8_t[]> value, |
| size_t value_size) |
| : property_type_(property_type), |
| value_(value.Pass()), |
| value_size_(value_size) { |
| } |
| |
| uint32_t DevicePropertyValue::AsUint32() const { |
| CHECK_EQ(property_type_, static_cast<DEVPROPTYPE>(DEVPROP_TYPE_UINT32)); |
| CHECK_EQ(value_size_, sizeof(uint32_t)); |
| return *reinterpret_cast<uint32_t*>(value_.get()); |
| } |
| |
| BluetoothLowEnergyServiceInfo::BluetoothLowEnergyServiceInfo() { |
| } |
| |
| BluetoothLowEnergyServiceInfo::~BluetoothLowEnergyServiceInfo() { |
| } |
| |
| BluetoothLowEnergyDeviceInfo::BluetoothLowEnergyDeviceInfo() |
| : visible(false), authenticated(false), connected(false) { |
| address.ullLong = BLUETOOTH_NULL_ADDRESS; |
| } |
| |
| BluetoothLowEnergyDeviceInfo::~BluetoothLowEnergyDeviceInfo() { |
| } |
| |
| bool IsBluetoothLowEnergySupported() { |
| return base::win::GetVersion() >= base::win::VERSION_WIN8; |
| } |
| |
| bool EnumerateKnownBluetoothLowEnergyDevices( |
| ScopedVector<BluetoothLowEnergyDeviceInfo>* devices, |
| std::string* error) { |
| if (!IsBluetoothLowEnergySupported()) { |
| *error = kPlatformNotSupported; |
| return false; |
| } |
| |
| ScopedDeviceInfoSetHandle info_set_handle; |
| HRESULT hr = OpenBluetoothLowEnergyDevices(&info_set_handle); |
| if (FAILED(hr)) { |
| *error = FormatBluetoothError(kDeviceEnumError, hr); |
| return false; |
| } |
| |
| for (DWORD i = 0;; ++i) { |
| scoped_ptr<BluetoothLowEnergyDeviceInfo> device_info; |
| DeviceInfoResult result = EnumerateSingleBluetoothLowEnergyDevice( |
| info_set_handle, i, &device_info, error); |
| switch (result) { |
| case kNoMoreDevices: |
| return true; |
| case kError: |
| return false; |
| case kOk: |
| devices->push_back(device_info.release()); |
| } |
| } |
| } |
| |
| bool EnumerateKnownBluetoothLowEnergyServices( |
| const base::FilePath& device_path, |
| ScopedVector<BluetoothLowEnergyServiceInfo>* services, |
| std::string* error) { |
| if (!IsBluetoothLowEnergySupported()) { |
| *error = kPlatformNotSupported; |
| return false; |
| } |
| |
| return CollectBluetoothLowEnergyDeviceServices(device_path, services, error); |
| } |
| |
| bool ExtractBluetoothAddressFromDeviceInstanceIdForTesting( |
| const std::string& instance_id, |
| BLUETOOTH_ADDRESS* btha, |
| std::string* error) { |
| return ExtractBluetoothAddressFromDeviceInstanceId(instance_id, btha, error); |
| } |
| |
| } // namespace win |
| } // namespace device |