| // Copyright 2018 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_device_winrt.h" |
| |
| #include <windows.devices.enumeration.h> |
| #include <windows.foundation.h> |
| |
| #include <utility> |
| |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/win/core_winrt_util.h" |
| #include "base/win/scoped_hstring.h" |
| #include "device/bluetooth/bluetooth_adapter_winrt.h" |
| #include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h" |
| #include "device/bluetooth/bluetooth_pairing_winrt.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h" |
| #include "device/bluetooth/event_utils_winrt.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus; |
| using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus_Connected; |
| using ABI::Windows::Devices::Bluetooth::BluetoothLEDevice; |
| using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: |
| GattCommunicationStatus; |
| using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: |
| GattCommunicationStatus_Success; |
| using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: |
| GattDeviceServicesResult; |
| using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: |
| IGattDeviceServicesResult; |
| using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice2; |
| using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3; |
| using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; |
| using ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics; |
| using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus; |
| using ABI::Windows::Devices::Enumeration::IDeviceInformation2; |
| using ABI::Windows::Devices::Enumeration::IDeviceInformation; |
| using ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing; |
| using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing2; |
| using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing; |
| using ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs; |
| using ABI::Windows::Foundation::IAsyncOperation; |
| using ABI::Windows::Foundation::IClosable; |
| using Microsoft::WRL::ComPtr; |
| |
| void PostTask(BluetoothPairingWinrt::ErrorCallback error_callback, |
| BluetoothDevice::ConnectErrorCode error_code) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(error_callback), error_code)); |
| } |
| |
| ComPtr<IDeviceInformationPairing> GetDeviceInformationPairing( |
| ComPtr<IBluetoothLEDevice> ble_device) { |
| if (!ble_device) { |
| VLOG(2) << "No BLE device instance present."; |
| return nullptr; |
| } |
| |
| ComPtr<IBluetoothLEDevice2> ble_device_2; |
| HRESULT hr = ble_device.As(&ble_device_2); |
| if (FAILED(hr)) { |
| VLOG(2) << "Obtaining IBluetoothLEDevice2 failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| ComPtr<IDeviceInformation> device_information; |
| hr = ble_device_2->get_DeviceInformation(&device_information); |
| if (FAILED(hr)) { |
| VLOG(2) << "Getting Device Information failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| ComPtr<IDeviceInformation2> device_information_2; |
| hr = device_information.As(&device_information_2); |
| if (FAILED(hr)) { |
| VLOG(2) << "Obtaining IDeviceInformation2 failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| ComPtr<IDeviceInformationPairing> pairing; |
| hr = device_information_2->get_Pairing(&pairing); |
| if (FAILED(hr)) { |
| VLOG(2) << "DeviceInformation::get_Pairing() failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| return pairing; |
| } |
| |
| void CloseDevice(ComPtr<IBluetoothLEDevice> ble_device) { |
| if (!ble_device) |
| return; |
| |
| ComPtr<IClosable> closable; |
| HRESULT hr = ble_device.As(&closable); |
| if (FAILED(hr)) { |
| VLOG(2) << "As IClosable failed: " << logging::SystemErrorCodeToString(hr); |
| return; |
| } |
| |
| hr = closable->Close(); |
| if (FAILED(hr)) { |
| VLOG(2) << "IClosable::close() failed: " |
| << logging::SystemErrorCodeToString(hr); |
| } |
| } |
| |
| void RemoveConnectionStatusHandler(IBluetoothLEDevice* ble_device, |
| EventRegistrationToken token) { |
| HRESULT hr = ble_device->remove_ConnectionStatusChanged(token); |
| if (FAILED(hr)) { |
| VLOG(2) << "Removing ConnectionStatus Handler failed: " |
| << logging::SystemErrorCodeToString(hr); |
| } |
| } |
| |
| void RemoveGattServicesChangedHandler(IBluetoothLEDevice* ble_device, |
| EventRegistrationToken token) { |
| HRESULT hr = ble_device->remove_GattServicesChanged(token); |
| if (FAILED(hr)) { |
| VLOG(2) << "Removing Gatt Services Changed Handler failed: " |
| << logging::SystemErrorCodeToString(hr); |
| } |
| } |
| |
| } // namespace |
| |
| BluetoothDeviceWinrt::BluetoothDeviceWinrt( |
| BluetoothAdapterWinrt* adapter, |
| uint64_t raw_address, |
| base::Optional<std::string> local_name) |
| : BluetoothDevice(adapter), |
| raw_address_(raw_address), |
| address_(CanonicalizeAddress(raw_address)), |
| local_name_(std::move(local_name)), |
| weak_ptr_factory_(this) {} |
| |
| BluetoothDeviceWinrt::~BluetoothDeviceWinrt() { |
| CloseDevice(ble_device_); |
| if (!connection_changed_token_) |
| return; |
| |
| RemoveConnectionStatusHandler(ble_device_.Get(), *connection_changed_token_); |
| } |
| |
| uint32_t BluetoothDeviceWinrt::GetBluetoothClass() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| std::string BluetoothDeviceWinrt::GetAddress() const { |
| return address_; |
| } |
| |
| BluetoothDevice::VendorIDSource BluetoothDeviceWinrt::GetVendorIDSource() |
| const { |
| NOTIMPLEMENTED(); |
| return VendorIDSource(); |
| } |
| |
| uint16_t BluetoothDeviceWinrt::GetVendorID() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| uint16_t BluetoothDeviceWinrt::GetProductID() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| uint16_t BluetoothDeviceWinrt::GetDeviceID() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| uint16_t BluetoothDeviceWinrt::GetAppearance() const { |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| base::Optional<std::string> BluetoothDeviceWinrt::GetName() const { |
| if (!ble_device_) |
| return local_name_; |
| |
| HSTRING name; |
| HRESULT hr = ble_device_->get_Name(&name); |
| if (FAILED(hr)) { |
| VLOG(2) << "Getting Name failed: " << logging::SystemErrorCodeToString(hr); |
| return local_name_; |
| } |
| |
| return base::win::ScopedHString(name).GetAsUTF8(); |
| } |
| |
| bool BluetoothDeviceWinrt::IsPaired() const { |
| ComPtr<IDeviceInformationPairing> pairing = |
| GetDeviceInformationPairing(ble_device_); |
| if (!pairing) { |
| VLOG(2) << "Failed to get DeviceInformationPairing."; |
| return false; |
| } |
| |
| boolean is_paired; |
| HRESULT hr = pairing->get_IsPaired(&is_paired); |
| if (FAILED(hr)) { |
| VLOG(2) << "DeviceInformationPairing::get_IsPaired() failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return false; |
| } |
| |
| VLOG(2) << "BluetoothDeviceWinrt::IsPaired(): " |
| << (is_paired ? "True" : "False"); |
| return is_paired; |
| } |
| |
| bool BluetoothDeviceWinrt::IsConnected() const { |
| return IsGattConnected(); |
| } |
| |
| bool BluetoothDeviceWinrt::IsGattConnected() const { |
| if (!ble_device_) |
| return false; |
| |
| BluetoothConnectionStatus status; |
| HRESULT hr = ble_device_->get_ConnectionStatus(&status); |
| if (FAILED(hr)) { |
| VLOG(2) << "Getting ConnectionStatus failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return false; |
| } |
| |
| return status == BluetoothConnectionStatus_Connected; |
| } |
| |
| bool BluetoothDeviceWinrt::IsConnectable() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| bool BluetoothDeviceWinrt::IsConnecting() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| bool BluetoothDeviceWinrt::ExpectingPinCode() const { |
| return pairing_ && pairing_->ExpectingPinCode(); |
| } |
| |
| bool BluetoothDeviceWinrt::ExpectingPasskey() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| bool BluetoothDeviceWinrt::ExpectingConfirmation() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void BluetoothDeviceWinrt::GetConnectionInfo( |
| const ConnectionInfoCallback& callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::SetConnectionLatency( |
| ConnectionLatency connection_latency, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::Connect(PairingDelegate* pairing_delegate, |
| const base::Closure& callback, |
| const ConnectErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::Pair(PairingDelegate* pairing_delegate, |
| const base::Closure& callback, |
| const ConnectErrorCallback& error_callback) { |
| VLOG(2) << "BluetoothDeviceWinrt::Pair()"; |
| if (pairing_) { |
| VLOG(2) << "Another Pair Operation is already in progress."; |
| PostTask(error_callback, ERROR_INPROGRESS); |
| return; |
| } |
| |
| ComPtr<IDeviceInformationPairing> pairing = |
| GetDeviceInformationPairing(ble_device_); |
| if (!pairing) { |
| VLOG(2) << "Failed to get DeviceInformationPairing."; |
| PostTask(error_callback, ERROR_UNKNOWN); |
| return; |
| } |
| |
| ComPtr<IDeviceInformationPairing2> pairing_2; |
| HRESULT hr = pairing.As(&pairing_2); |
| if (FAILED(hr)) { |
| VLOG(2) << "Obtaining IDeviceInformationPairing2 failed: " |
| << logging::SystemErrorCodeToString(hr); |
| PostTask(error_callback, ERROR_UNKNOWN); |
| return; |
| } |
| |
| ComPtr<IDeviceInformationCustomPairing> custom; |
| hr = pairing_2->get_Custom(&custom); |
| if (FAILED(hr)) { |
| VLOG(2) << "DeviceInformationPairing::get_Custom() failed: " |
| << logging::SystemErrorCodeToString(hr); |
| PostTask(error_callback, ERROR_UNKNOWN); |
| return; |
| } |
| |
| // Wrap success and error callback, so that they clean up the pairing object |
| // once they are run. |
| auto wrapped_callback = base::BindOnce( |
| [](base::WeakPtr<BluetoothDeviceWinrt> device, |
| base::OnceClosure callback) { |
| if (device) |
| device->pairing_.reset(); |
| std::move(callback).Run(); |
| }, |
| weak_ptr_factory_.GetWeakPtr(), callback); |
| |
| auto wrapped_error_callback = base::BindOnce( |
| [](base::WeakPtr<BluetoothDeviceWinrt> device, |
| ConnectErrorCallback error_callback, ConnectErrorCode error_code) { |
| if (device) |
| device->pairing_.reset(); |
| std::move(error_callback).Run(error_code); |
| }, |
| weak_ptr_factory_.GetWeakPtr(), error_callback); |
| |
| pairing_ = std::make_unique<BluetoothPairingWinrt>( |
| this, pairing_delegate, std::move(custom), std::move(wrapped_callback), |
| std::move(wrapped_error_callback)); |
| pairing_->StartPairing(); |
| } |
| |
| void BluetoothDeviceWinrt::SetPinCode(const std::string& pincode) { |
| if (pairing_) |
| pairing_->SetPinCode(pincode); |
| } |
| |
| void BluetoothDeviceWinrt::SetPasskey(uint32_t passkey) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::ConfirmPairing() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::RejectPairing() { |
| if (pairing_) |
| pairing_->RejectPairing(); |
| } |
| |
| void BluetoothDeviceWinrt::CancelPairing() { |
| if (pairing_) |
| pairing_->CancelPairing(); |
| } |
| |
| void BluetoothDeviceWinrt::Disconnect(const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::Forget(const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::ConnectToService( |
| const BluetoothUUID& uuid, |
| const ConnectToServiceCallback& callback, |
| const ConnectToServiceErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void BluetoothDeviceWinrt::ConnectToServiceInsecurely( |
| const device::BluetoothUUID& uuid, |
| const ConnectToServiceCallback& callback, |
| const ConnectToServiceErrorCallback& error_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| // static |
| std::string BluetoothDeviceWinrt::CanonicalizeAddress(uint64_t address) { |
| std::string bluetooth_address = BluetoothDevice::CanonicalizeAddress( |
| base::StringPrintf("%012llX", address)); |
| DCHECK(!bluetooth_address.empty()); |
| return bluetooth_address; |
| } |
| |
| void BluetoothDeviceWinrt::CreateGattConnectionImpl() { |
| ComPtr<IBluetoothLEDeviceStatics> device_statics; |
| HRESULT hr = GetBluetoothLEDeviceStaticsActivationFactory(&device_statics); |
| if (FAILED(hr)) { |
| VLOG(2) << "GetBluetoothLEDeviceStaticsActivationFactory failed: " |
| << logging::SystemErrorCodeToString(hr); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, |
| weak_ptr_factory_.GetWeakPtr(), |
| ConnectErrorCode::ERROR_FAILED)); |
| return; |
| } |
| |
| // Note: Even though we might have obtained a BluetoothLEDevice instance in |
| // the past, we need to request a new instance as the old device might have |
| // been closed. See also |
| // https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client#connecting-to-the-device |
| ComPtr<IAsyncOperation<BluetoothLEDevice*>> from_bluetooth_address_op; |
| hr = device_statics->FromBluetoothAddressAsync(raw_address_, |
| &from_bluetooth_address_op); |
| if (FAILED(hr)) { |
| VLOG(2) << "BluetoothLEDevice::FromBluetoothAddressAsync failed: " |
| << logging::SystemErrorCodeToString(hr); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, |
| weak_ptr_factory_.GetWeakPtr(), |
| ConnectErrorCode::ERROR_FAILED)); |
| return; |
| } |
| |
| hr = PostAsyncResults( |
| std::move(from_bluetooth_address_op), |
| base::BindOnce(&BluetoothDeviceWinrt::OnFromBluetoothAddress, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| if (FAILED(hr)) { |
| VLOG(2) << "PostAsyncResults failed: " |
| << logging::SystemErrorCodeToString(hr); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, |
| weak_ptr_factory_.GetWeakPtr(), |
| ConnectErrorCode::ERROR_FAILED)); |
| } |
| } |
| |
| void BluetoothDeviceWinrt::DisconnectGatt() { |
| // Closing the device and disposing of all references will trigger a Gatt |
| // Disconnection after a short timeout. Since the Gatt Services store a |
| // reference to |ble_device_| as well, we need to clear them to drop all |
| // remaining references, so that the OS disconnects. |
| // Reference: |
| // - https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client |
| CloseDevice(ble_device_); |
| ClearGattServices(); |
| |
| // Stop any pending Gatt Discovery sessions and report an error. This will |
| // destroy |gatt_discoverer_| and release remaining references the discoverer |
| // might have hold. |
| if (gatt_discoverer_) |
| OnGattDiscoveryComplete(false); |
| } |
| |
| HRESULT BluetoothDeviceWinrt::GetBluetoothLEDeviceStaticsActivationFactory( |
| IBluetoothLEDeviceStatics** statics) const { |
| return base::win::GetActivationFactory< |
| IBluetoothLEDeviceStatics, |
| RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice>(statics); |
| } |
| |
| void BluetoothDeviceWinrt::OnFromBluetoothAddress( |
| ComPtr<IBluetoothLEDevice> ble_device) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (!ble_device) { |
| VLOG(2) << "Getting Device From Bluetooth Address failed."; |
| DidFailToConnectGatt(ConnectErrorCode::ERROR_FAILED); |
| return; |
| } |
| |
| if (connection_changed_token_) { |
| // As we are about to replace |ble_device_| with |ble_device| we will also |
| // unregister the existing event handler and add a new one to the new |
| // device. |
| RemoveConnectionStatusHandler(ble_device_.Get(), |
| *connection_changed_token_); |
| } |
| |
| ble_device_ = std::move(ble_device); |
| connection_changed_token_ = AddTypedEventHandler( |
| ble_device_.Get(), &IBluetoothLEDevice::add_ConnectionStatusChanged, |
| base::BindRepeating(&BluetoothDeviceWinrt::OnConnectionStatusChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // For paired devices the OS immediately establishes a GATT connection after |
| // the first advertisement. In this case our handler is registered too late to |
| // catch the initial connection changed event, and we need to perform an |
| // explicit check ourselves. |
| if (IsGattConnected()) |
| DidConnectGatt(); |
| |
| if (gatt_services_changed_token_) { |
| RemoveGattServicesChangedHandler(ble_device_.Get(), |
| *gatt_services_changed_token_); |
| } |
| |
| gatt_services_changed_token_ = AddTypedEventHandler( |
| ble_device_.Get(), &IBluetoothLEDevice::add_GattServicesChanged, |
| base::BindRepeating(&BluetoothDeviceWinrt::OnGattServicesChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| gatt_discoverer_ = |
| std::make_unique<BluetoothGattDiscovererWinrt>(ble_device_); |
| // Initiating the GATT discovery will result in a GATT connection attempt as |
| // well and triggers OnConnectionStatusChanged on success. |
| gatt_discoverer_->StartGattDiscovery( |
| base::BindOnce(&BluetoothDeviceWinrt::OnGattDiscoveryComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BluetoothDeviceWinrt::OnConnectionStatusChanged( |
| IBluetoothLEDevice* ble_device, |
| IInspectable* object) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (IsGattConnected()) { |
| DidConnectGatt(); |
| } else { |
| gatt_discoverer_.reset(); |
| ClearGattServices(); |
| DidDisconnectGatt(); |
| } |
| } |
| |
| void BluetoothDeviceWinrt::OnGattServicesChanged(IBluetoothLEDevice* ble_device, |
| IInspectable* object) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // Note: We don't clear out |gatt_services_| here, as we don't want to break |
| // existing references to Gatt Services that did not change. |
| device_uuids_.ClearServiceUUIDs(); |
| SetGattServicesDiscoveryComplete(false); |
| adapter_->NotifyDeviceChanged(this); |
| if (IsGattConnected()) { |
| // In order to stop a potential ongoing GATT discovery, the GattDiscoverer |
| // is reset and a new discovery is initiated. |
| gatt_discoverer_ = |
| std::make_unique<BluetoothGattDiscovererWinrt>(ble_device); |
| gatt_discoverer_->StartGattDiscovery( |
| base::BindOnce(&BluetoothDeviceWinrt::OnGattDiscoveryComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void BluetoothDeviceWinrt::OnGattDiscoveryComplete(bool success) { |
| if (!success) { |
| if (!IsGattConnected()) |
| DidFailToConnectGatt(ConnectErrorCode::ERROR_FAILED); |
| gatt_discoverer_.reset(); |
| return; |
| } |
| |
| // Instead of clearing out |gatt_services_| and creating each service from |
| // scratch, we create a new map and move already existing services into it in |
| // order to preserve pointer stability. |
| GattServiceMap gatt_services; |
| for (const auto& gatt_service : gatt_discoverer_->GetGattServices()) { |
| auto gatt_service_winrt = |
| BluetoothRemoteGattServiceWinrt::Create(this, gatt_service); |
| if (!gatt_service_winrt) |
| continue; |
| |
| std::string identifier = gatt_service_winrt->GetIdentifier(); |
| auto iter = gatt_services_.find(identifier); |
| if (iter != gatt_services_.end()) { |
| iter = gatt_services.emplace(std::move(*iter)).first; |
| } else { |
| iter = gatt_services |
| .emplace(std::move(identifier), std::move(gatt_service_winrt)) |
| .first; |
| } |
| |
| static_cast<BluetoothRemoteGattServiceWinrt*>(iter->second.get()) |
| ->UpdateCharacteristics(gatt_discoverer_.get()); |
| } |
| |
| std::swap(gatt_services, gatt_services_); |
| device_uuids_.ReplaceServiceUUIDs(gatt_services_); |
| SetGattServicesDiscoveryComplete(true); |
| adapter_->NotifyGattServicesDiscovered(this); |
| adapter_->NotifyDeviceChanged(this); |
| gatt_discoverer_.reset(); |
| } |
| |
| void BluetoothDeviceWinrt::ClearGattServices() { |
| gatt_services_.clear(); |
| device_uuids_.ClearServiceUUIDs(); |
| SetGattServicesDiscoveryComplete(false); |
| } |
| |
| } // namespace device |