| // Copyright (c) 2012 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.h" |
| |
| #include <array> |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_gatt_connection.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_descriptor.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_service.h" |
| #include "device/bluetooth/public/cpp/bluetooth_uuid.h" |
| #include "device/bluetooth/string_util_icu.h" |
| #include "device/bluetooth/strings/grit/bluetooth_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace device { |
| |
| BluetoothDevice::DeviceUUIDs::DeviceUUIDs() = default; |
| |
| BluetoothDevice::DeviceUUIDs::~DeviceUUIDs() = default; |
| |
| BluetoothDevice::DeviceUUIDs::DeviceUUIDs(const DeviceUUIDs& other) = default; |
| |
| BluetoothDevice::DeviceUUIDs& BluetoothDevice::DeviceUUIDs::operator=( |
| const DeviceUUIDs& other) = default; |
| |
| void BluetoothDevice::DeviceUUIDs::ReplaceAdvertisedUUIDs( |
| UUIDList new_advertised_uuids) { |
| advertised_uuids_.clear(); |
| for (auto& it : new_advertised_uuids) { |
| advertised_uuids_.insert(std::move(it)); |
| } |
| UpdateDeviceUUIDs(); |
| } |
| |
| void BluetoothDevice::DeviceUUIDs::ClearAdvertisedUUIDs() { |
| advertised_uuids_.clear(); |
| UpdateDeviceUUIDs(); |
| } |
| |
| void BluetoothDevice::DeviceUUIDs::ReplaceServiceUUIDs( |
| const GattServiceMap& gatt_services) { |
| service_uuids_.clear(); |
| for (const auto& gatt_service_pair : gatt_services) |
| service_uuids_.insert(gatt_service_pair.second->GetUUID()); |
| UpdateDeviceUUIDs(); |
| } |
| |
| void BluetoothDevice::DeviceUUIDs::ReplaceServiceUUIDs( |
| UUIDList new_service_uuids) { |
| service_uuids_.clear(); |
| for (auto& it : new_service_uuids) { |
| service_uuids_.insert(std::move(it)); |
| } |
| UpdateDeviceUUIDs(); |
| } |
| |
| void BluetoothDevice::DeviceUUIDs::ClearServiceUUIDs() { |
| service_uuids_.clear(); |
| UpdateDeviceUUIDs(); |
| } |
| |
| const BluetoothDevice::UUIDSet& BluetoothDevice::DeviceUUIDs::GetUUIDs() const { |
| return device_uuids_; |
| } |
| |
| void BluetoothDevice::DeviceUUIDs::UpdateDeviceUUIDs() { |
| device_uuids_ = base::STLSetUnion<BluetoothDevice::UUIDSet>(advertised_uuids_, |
| service_uuids_); |
| } |
| |
| BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) |
| : adapter_(adapter), |
| gatt_services_discovery_complete_(false), |
| last_update_time_(base::Time()) {} |
| |
| BluetoothDevice::~BluetoothDevice() { |
| for (BluetoothGattConnection* connection : gatt_connections_) { |
| connection->InvalidateConnectionReference(); |
| } |
| } |
| |
| BluetoothDevice::ConnectionInfo::ConnectionInfo() |
| : rssi(kUnknownPower), |
| transmit_power(kUnknownPower), |
| max_transmit_power(kUnknownPower) {} |
| |
| BluetoothDevice::ConnectionInfo::ConnectionInfo(int rssi, |
| int transmit_power, |
| int max_transmit_power) |
| : rssi(rssi), |
| transmit_power(transmit_power), |
| max_transmit_power(max_transmit_power) {} |
| |
| BluetoothDevice::ConnectionInfo::~ConnectionInfo() = default; |
| |
| std::u16string BluetoothDevice::GetNameForDisplay() const { |
| absl::optional<std::string> name = GetName(); |
| if (name && HasGraphicCharacter(name.value())) { |
| return base::UTF8ToUTF16(name.value()); |
| } else { |
| return GetAddressWithLocalizedDeviceTypeName(); |
| } |
| } |
| |
| std::u16string BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() const { |
| std::u16string address_utf16 = base::UTF8ToUTF16(GetAddress()); |
| BluetoothDeviceType device_type = GetDeviceType(); |
| switch (device_type) { |
| case BluetoothDeviceType::COMPUTER: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_COMPUTER, |
| address_utf16); |
| case BluetoothDeviceType::PHONE: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_PHONE, |
| address_utf16); |
| case BluetoothDeviceType::MODEM: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_MODEM, |
| address_utf16); |
| case BluetoothDeviceType::AUDIO: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_AUDIO, |
| address_utf16); |
| case BluetoothDeviceType::CAR_AUDIO: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_CAR_AUDIO, |
| address_utf16); |
| case BluetoothDeviceType::VIDEO: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_VIDEO, |
| address_utf16); |
| case BluetoothDeviceType::JOYSTICK: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_JOYSTICK, |
| address_utf16); |
| case BluetoothDeviceType::GAMEPAD: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_GAMEPAD, |
| address_utf16); |
| case BluetoothDeviceType::KEYBOARD: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_KEYBOARD, |
| address_utf16); |
| case BluetoothDeviceType::MOUSE: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_MOUSE, |
| address_utf16); |
| case BluetoothDeviceType::TABLET: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_TABLET, |
| address_utf16); |
| case BluetoothDeviceType::KEYBOARD_MOUSE_COMBO: |
| return l10n_util::GetStringFUTF16( |
| IDS_BLUETOOTH_DEVICE_KEYBOARD_MOUSE_COMBO, address_utf16); |
| default: |
| return l10n_util::GetStringFUTF16(IDS_BLUETOOTH_DEVICE_UNKNOWN, |
| address_utf16); |
| } |
| } |
| |
| BluetoothDeviceType BluetoothDevice::GetDeviceType() const { |
| // https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm |
| uint32_t bluetooth_class = GetBluetoothClass(); |
| switch ((bluetooth_class & 0x1f00) >> 8) { |
| case 0x01: |
| // Computer major device class. |
| return BluetoothDeviceType::COMPUTER; |
| case 0x02: |
| // Phone major device class. |
| switch ((bluetooth_class & 0xfc) >> 2) { |
| case 0x01: |
| case 0x02: |
| case 0x03: |
| // Cellular, cordless and smart phones. |
| return BluetoothDeviceType::PHONE; |
| case 0x04: |
| case 0x05: |
| // Modems: wired or voice gateway and common ISDN access. |
| return BluetoothDeviceType::MODEM; |
| } |
| break; |
| case 0x04: |
| // Audio major device class. |
| switch ((bluetooth_class & 0xfc) >> 2) { |
| case 0x08: |
| // Car audio. |
| return BluetoothDeviceType::CAR_AUDIO; |
| case 0x0b: |
| case 0x0c: |
| case 0x0d: |
| case 0x0e: |
| case 0x0f: |
| case 0x010: |
| // Video devices. |
| return BluetoothDeviceType::VIDEO; |
| default: |
| return BluetoothDeviceType::AUDIO; |
| } |
| case 0x05: |
| // Peripheral major device class. |
| switch ((bluetooth_class & 0xc0) >> 6) { |
| case 0x00: |
| // "Not a keyboard or pointing device." |
| switch ((bluetooth_class & 0x01e) >> 2) { |
| case 0x01: |
| // Joystick. |
| return BluetoothDeviceType::JOYSTICK; |
| case 0x02: |
| // Gamepad. |
| return BluetoothDeviceType::GAMEPAD; |
| default: |
| return BluetoothDeviceType::PERIPHERAL; |
| } |
| case 0x01: |
| // Keyboard. |
| return BluetoothDeviceType::KEYBOARD; |
| case 0x02: |
| // Pointing device. |
| switch ((bluetooth_class & 0x01e) >> 2) { |
| case 0x05: |
| // Digitizer tablet. |
| return BluetoothDeviceType::TABLET; |
| default: |
| // Mouse. |
| return BluetoothDeviceType::MOUSE; |
| } |
| case 0x03: |
| // Combo device. |
| return BluetoothDeviceType::KEYBOARD_MOUSE_COMBO; |
| } |
| break; |
| } |
| |
| // Some bluetooth devices, e.g., Microsoft Universal Foldable Keyboard, |
| // do not expose its bluetooth class. Use its appearance as a work-around. |
| // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml |
| uint16_t appearance = GetAppearance(); |
| // appearance: 10-bit category and 6-bit sub-category |
| switch ((appearance & 0xffc0) >> 6) { |
| case 0x01: |
| // Generic phone |
| return BluetoothDeviceType::PHONE; |
| case 0x02: |
| // Generic computer |
| return BluetoothDeviceType::COMPUTER; |
| case 0x0f: |
| // HID subtype |
| switch (appearance & 0x3f) { |
| case 0x01: |
| // Keyboard. |
| return BluetoothDeviceType::KEYBOARD; |
| case 0x02: |
| // Mouse |
| return BluetoothDeviceType::MOUSE; |
| case 0x03: |
| // Joystick |
| return BluetoothDeviceType::JOYSTICK; |
| case 0x04: |
| // Gamepad |
| return BluetoothDeviceType::GAMEPAD; |
| case 0x05: |
| // Digitizer tablet |
| return BluetoothDeviceType::TABLET; |
| } |
| } |
| |
| return BluetoothDeviceType::UNKNOWN; |
| } |
| |
| bool BluetoothDevice::IsPairable() const { |
| BluetoothDeviceType type = GetDeviceType(); |
| |
| // Get the vendor part of the address: "00:11:22" for "00:11:22:33:44:55" |
| std::string vendor = GetAddress().substr(0, 8); |
| |
| // Verbatim "Bluetooth Mouse", model 96674 |
| if (type == BluetoothDeviceType::MOUSE && vendor == "00:12:A1") |
| return false; |
| // Microsoft "Microsoft Bluetooth Notebook Mouse 5000", model X807028-001 |
| if (type == BluetoothDeviceType::MOUSE && vendor == "7C:ED:8D") |
| return false; |
| |
| // TODO: Move this database into a config file. |
| |
| return true; |
| } |
| |
| BluetoothDevice::UUIDSet BluetoothDevice::GetUUIDs() const { |
| return device_uuids_.GetUUIDs(); |
| } |
| |
| const BluetoothDevice::ServiceDataMap& BluetoothDevice::GetServiceData() const { |
| return service_data_; |
| } |
| |
| BluetoothDevice::UUIDSet BluetoothDevice::GetServiceDataUUIDs() const { |
| UUIDSet service_data_uuids; |
| for (const auto& uuid_service_data_pair : service_data_) { |
| service_data_uuids.insert(uuid_service_data_pair.first); |
| } |
| return service_data_uuids; |
| } |
| |
| const std::vector<uint8_t>* BluetoothDevice::GetServiceDataForUUID( |
| const BluetoothUUID& uuid) const { |
| auto it = service_data_.find(uuid); |
| if (it != service_data_.end()) { |
| return &it->second; |
| } |
| return nullptr; |
| } |
| |
| const BluetoothDevice::ManufacturerDataMap& |
| BluetoothDevice::GetManufacturerData() const { |
| return manufacturer_data_; |
| } |
| |
| BluetoothDevice::ManufacturerIDSet BluetoothDevice::GetManufacturerDataIDs() |
| const { |
| ManufacturerIDSet manufacturer_data_ids; |
| for (const auto& manufacturer_data_pair : manufacturer_data_) { |
| manufacturer_data_ids.insert(manufacturer_data_pair.first); |
| } |
| return manufacturer_data_ids; |
| } |
| |
| const std::vector<uint8_t>* BluetoothDevice::GetManufacturerDataForID( |
| const ManufacturerId manufacturerID) const { |
| auto it = manufacturer_data_.find(manufacturerID); |
| if (it != manufacturer_data_.end()) { |
| return &it->second; |
| } |
| return nullptr; |
| } |
| |
| absl::optional<int8_t> BluetoothDevice::GetInquiryRSSI() const { |
| return inquiry_rssi_; |
| } |
| |
| absl::optional<uint8_t> BluetoothDevice::GetAdvertisingDataFlags() const { |
| return advertising_data_flags_; |
| } |
| |
| absl::optional<int8_t> BluetoothDevice::GetInquiryTxPower() const { |
| return inquiry_tx_power_; |
| } |
| |
| void BluetoothDevice::CreateGattConnection( |
| GattConnectionCallback callback, |
| absl::optional<BluetoothUUID> service_uuid) { |
| if (!supports_service_specific_discovery_) |
| service_uuid.reset(); |
| |
| const bool connection_already_pending = |
| !create_gatt_connection_callbacks_.empty(); |
| |
| create_gatt_connection_callbacks_.push_back(std::move(callback)); |
| |
| // If a service-specific discovery was originally requested, but this request |
| // is for a different or non-specific discovery, then the previous discovery |
| // needs to be redone. |
| if (target_service_.has_value() && target_service_ != service_uuid) { |
| DCHECK(IsGattConnected() || connection_already_pending); |
| target_service_ = service_uuid; |
| UpgradeToFullDiscovery(); |
| } |
| |
| if (IsGattConnected()) { |
| DCHECK(!connection_already_pending); |
| return DidConnectGatt(/*error_code=*/absl::nullopt); |
| } |
| |
| if (connection_already_pending) { |
| // The correct callback will be run when the existing connection attempt |
| // completes. |
| return; |
| } |
| |
| target_service_ = service_uuid; |
| CreateGattConnectionImpl(std::move(service_uuid)); |
| } |
| |
| void BluetoothDevice::SetGattServicesDiscoveryComplete(bool complete) { |
| gatt_services_discovery_complete_ = complete; |
| } |
| |
| bool BluetoothDevice::IsGattServicesDiscoveryComplete() const { |
| return !target_service_ && gatt_services_discovery_complete_; |
| } |
| |
| std::vector<BluetoothRemoteGattService*> BluetoothDevice::GetGattServices() |
| const { |
| std::vector<BluetoothRemoteGattService*> services; |
| for (const auto& iter : gatt_services_) |
| services.push_back(iter.second.get()); |
| return services; |
| } |
| |
| BluetoothRemoteGattService* BluetoothDevice::GetGattService( |
| const std::string& identifier) const { |
| auto it = gatt_services_.find(identifier); |
| if (it == gatt_services_.end()) |
| return nullptr; |
| return it->second.get(); |
| } |
| |
| std::string BluetoothDevice::GetIdentifier() const { |
| return GetAddress(); |
| } |
| |
| void BluetoothDevice::UpdateAdvertisementData( |
| int8_t rssi, |
| absl::optional<uint8_t> flags, |
| UUIDList advertised_uuids, |
| absl::optional<int8_t> tx_power, |
| ServiceDataMap service_data, |
| ManufacturerDataMap manufacturer_data) { |
| UpdateTimestamp(); |
| |
| inquiry_rssi_ = rssi; |
| advertising_data_flags_ = std::move(flags); |
| device_uuids_.ReplaceAdvertisedUUIDs(std::move(advertised_uuids)); |
| inquiry_tx_power_ = std::move(tx_power); |
| service_data_ = std::move(service_data); |
| manufacturer_data_ = std::move(manufacturer_data); |
| } |
| |
| void BluetoothDevice::ClearAdvertisementData() { |
| inquiry_rssi_.reset(); |
| advertising_data_flags_.reset(); |
| inquiry_tx_power_.reset(); |
| device_uuids_.ClearAdvertisedUUIDs(); |
| service_data_.clear(); |
| manufacturer_data_.clear(); |
| GetAdapter()->NotifyDeviceChanged(this); |
| } |
| |
| std::vector<BluetoothRemoteGattService*> BluetoothDevice::GetPrimaryServices() { |
| std::vector<BluetoothRemoteGattService*> services; |
| DVLOG(2) << "Looking for services."; |
| for (BluetoothRemoteGattService* service : GetGattServices()) { |
| DVLOG(2) << "Service in cache: " << service->GetUUID().canonical_value(); |
| if (service->IsPrimary()) { |
| services.push_back(service); |
| } |
| } |
| return services; |
| } |
| |
| std::vector<BluetoothRemoteGattService*> |
| BluetoothDevice::GetPrimaryServicesByUUID(const BluetoothUUID& service_uuid) { |
| std::vector<BluetoothRemoteGattService*> services; |
| DVLOG(2) << "Looking for service: " << service_uuid.canonical_value(); |
| for (BluetoothRemoteGattService* service : GetGattServices()) { |
| DVLOG(2) << "Service in cache: " << service->GetUUID().canonical_value(); |
| if (service->GetUUID() == service_uuid && service->IsPrimary()) { |
| services.push_back(service); |
| } |
| } |
| return services; |
| } |
| |
| #if defined(OS_CHROMEOS) || defined(OS_LINUX) |
| void BluetoothDevice::SetBatteryPercentage( |
| absl::optional<uint8_t> battery_percentage) { |
| if (battery_percentage) |
| DCHECK_LE(battery_percentage.value(), 100); |
| |
| if (battery_percentage_ == battery_percentage) |
| return; |
| |
| battery_percentage_ = battery_percentage; |
| GetAdapter()->NotifyDeviceBatteryChanged(this); |
| } |
| #endif |
| |
| bool BluetoothDevice::supports_service_specific_discovery() const { |
| return supports_service_specific_discovery_; |
| } |
| |
| void BluetoothDevice::UpgradeToFullDiscovery() { |
| // Must be overridden by any subclass that sets |
| // |supports_service_specific_discovery_|. |
| NOTREACHED(); |
| } |
| |
| std::unique_ptr<BluetoothGattConnection> |
| BluetoothDevice::CreateBluetoothGattConnectionObject() { |
| return std::make_unique<BluetoothGattConnection>(adapter_, GetAddress()); |
| } |
| |
| void BluetoothDevice::DidConnectGatt(absl::optional<ConnectErrorCode> error) { |
| if (error.has_value()) { |
| // Connection request should only be made if there are no active |
| // connections. |
| DCHECK(gatt_connections_.empty()); |
| |
| target_service_.reset(); |
| |
| for (auto& callback : create_gatt_connection_callbacks_) |
| std::move(callback).Run(/*connection=*/nullptr, error.value()); |
| create_gatt_connection_callbacks_.clear(); |
| return; |
| } |
| |
| for (auto& callback : create_gatt_connection_callbacks_) { |
| std::move(callback).Run(CreateBluetoothGattConnectionObject(), |
| /*error_code=*/absl::nullopt); |
| } |
| |
| create_gatt_connection_callbacks_.clear(); |
| GetAdapter()->NotifyDeviceChanged(this); |
| } |
| |
| void BluetoothDevice::DidDisconnectGatt() { |
| // Pending calls to connect GATT are not expected, if they were then |
| // DidConnectGatt should have been called. |
| DCHECK(create_gatt_connection_callbacks_.empty()); |
| |
| target_service_.reset(); |
| |
| // Invalidate all BluetoothGattConnection objects. |
| for (BluetoothGattConnection* connection : gatt_connections_) { |
| connection->InvalidateConnectionReference(); |
| } |
| gatt_connections_.clear(); |
| GetAdapter()->NotifyDeviceChanged(this); |
| } |
| |
| void BluetoothDevice::AddGattConnection(BluetoothGattConnection* connection) { |
| auto result = gatt_connections_.insert(connection); |
| DCHECK(result.second); // Check insert happened; there was no duplicate. |
| } |
| |
| void BluetoothDevice::RemoveGattConnection( |
| BluetoothGattConnection* connection) { |
| size_t erased_count = gatt_connections_.erase(connection); |
| DCHECK(erased_count); |
| if (gatt_connections_.size() == 0) |
| DisconnectGatt(); |
| } |
| |
| void BluetoothDevice::SetAsExpiredForTesting() { |
| last_update_time_ = |
| base::Time::NowFromSystemTime() - |
| (BluetoothAdapter::timeoutSec + base::TimeDelta::FromSeconds(1)); |
| } |
| |
| void BluetoothDevice::Pair(PairingDelegate* pairing_delegate, |
| ConnectCallback callback) { |
| NOTREACHED(); |
| } |
| |
| void BluetoothDevice::UpdateTimestamp() { |
| last_update_time_ = base::Time::NowFromSystemTime(); |
| } |
| |
| base::Time BluetoothDevice::GetLastUpdateTime() const { |
| return last_update_time_; |
| } |
| |
| // static |
| int8_t BluetoothDevice::ClampPower(int power) { |
| if (power < INT8_MIN) { |
| return INT8_MIN; |
| } |
| if (power > INT8_MAX) { |
| return INT8_MAX; |
| } |
| return static_cast<int8_t>(power); |
| } |
| |
| } // namespace device |