| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/quick_pair/message_stream/message_stream_lookup_impl.h" |
| |
| #include "ash/quick_pair/common/constants.h" |
| #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h" |
| #include "base/containers/contains.h" |
| #include "components/cross_device/logging/logging.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| #include "device/bluetooth/bluetooth_device.h" |
| #include "device/bluetooth/bluetooth_socket.h" |
| |
| namespace { |
| |
| const device::BluetoothUUID kMessageStreamUuid( |
| "df21fe2c-2515-4fdb-8886-f12c4d67927c"); |
| constexpr int kMaxCreateMessageStreamAttempts{6}; |
| |
| // Attempt retry `n` after cooldown period |message_retry_cooldowns[n-1]|. |
| // These cooldown periods replicate those that Android's Fast Pair service |
| // mandates. |
| const std::vector<base::TimeDelta> kCreateMessageStreamRetryCooldowns{ |
| base::Seconds(2), base::Seconds(4), base::Seconds(8), base::Seconds(16), |
| base::Seconds(32)}; |
| |
| } // namespace |
| |
| namespace ash { |
| namespace quick_pair { |
| |
| std::string MessageStreamLookupImpl::CreateMessageStreamAttemptTypeToString( |
| const CreateMessageStreamAttemptType& type) { |
| switch (type) { |
| case CreateMessageStreamAttemptType::kDeviceConnectedStateChanged: |
| return "[DeviceConnectedStateChanged]"; |
| case CreateMessageStreamAttemptType::kDeviceAdded: |
| return "[DeviceAdded]"; |
| case CreateMessageStreamAttemptType::kDevicePairedChanged: |
| return "[DevicePairedChanged]"; |
| case CreateMessageStreamAttemptType::kDeviceChanged: |
| return "[DeviceChanged]"; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| MessageStreamLookupImpl::MessageStreamLookupImpl() { |
| device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce( |
| &MessageStreamLookupImpl::OnGetAdapter, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void MessageStreamLookupImpl::OnGetAdapter( |
| scoped_refptr<device::BluetoothAdapter> adapter) { |
| adapter_ = adapter; |
| adapter_observation_.Observe(adapter_.get()); |
| } |
| |
| void MessageStreamLookupImpl::AddObserver( |
| MessageStreamLookup::Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void MessageStreamLookupImpl::RemoveObserver( |
| MessageStreamLookup::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| MessageStreamLookupImpl::~MessageStreamLookupImpl() = default; |
| |
| MessageStream* MessageStreamLookupImpl::GetMessageStream( |
| const std::string& device_address) { |
| auto it = message_streams_.find(device_address); |
| // If we don't have a MessageStream for the device at |device_address|, return |
| // a nullptr. |
| if (it == message_streams_.end()) |
| return nullptr; |
| |
| // Return the pointer underneath the unique_ptr to the MessageStream we are |
| // owning for the device at |device_address|. |
| return it->second.get(); |
| } |
| |
| void MessageStreamLookupImpl::DevicePairedChanged( |
| device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device, |
| bool new_paired_status) { |
| // This event is triggered for all paired devices when BT is toggled on, so it |
| // is important to make sure the device is actively connected or a connection |
| // attempt will be issued for the Message Stream service UUID which prevents |
| // audio profiles from connecting. |
| if (!device->IsConnected()) { |
| return; |
| } |
| |
| // Check to see if the device supports Message Streams. |
| if (!device || !base::Contains(device->GetUUIDs(), kMessageStreamUuid)) { |
| return; |
| } |
| |
| // Remove and delete the memory stream for the device, if it exists. |
| if (!new_paired_status) { |
| AttemptRemoveMessageStream(device->GetAddress()); |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": Attempting to create MessageStream for device = [" |
| << device->GetAddress() << "] " << device->GetNameForDisplay(); |
| AttemptCreateMessageStream( |
| device->GetAddress(), |
| CreateMessageStreamAttemptType::kDevicePairedChanged); |
| } |
| |
| void MessageStreamLookupImpl::DeviceConnectedStateChanged( |
| device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device, |
| bool is_now_connected) { |
| // Check to see if the device supports Message Streams. |
| if (!device || !device->IsPaired() || |
| !base::Contains(device->GetUUIDs(), kMessageStreamUuid)) { |
| return; |
| } |
| |
| // Remove and delete the memory stream for the device, if it exists. |
| if (!is_now_connected) { |
| AttemptRemoveMessageStream(device->GetAddress()); |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": Attempting to create MessageStream for device = [" |
| << device->GetAddress() << "] " << device->GetNameForDisplay(); |
| AttemptCreateMessageStream( |
| device->GetAddress(), |
| CreateMessageStreamAttemptType::kDeviceConnectedStateChanged); |
| } |
| |
| void MessageStreamLookupImpl::DeviceChanged(device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device) { |
| // Check to see if the device is connected and supports MessageStreams. We |
| // need to check if the device is both connected and paired to the adapter |
| // because it is possible for a device to be connected to the adapter but not |
| // paired (example: a request for the adapter's SDP records). |
| if (!device || !(device->IsConnected() && device->IsPaired()) || |
| !base::Contains(device->GetUUIDs(), kMessageStreamUuid)) { |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ |
| << ": found connected device. Attempting to create " |
| "MessageStream for device = [" |
| << device->GetAddress() << "] " << device->GetNameForDisplay(); |
| AttemptCreateMessageStream(device->GetAddress(), |
| CreateMessageStreamAttemptType::kDeviceChanged); |
| } |
| |
| void MessageStreamLookupImpl::DeviceAdded(device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device) { |
| // Check to see if the device is connected and supports MessageStreams. We |
| // need to check if the device is both connected and paired to the adapter |
| // because it is possible for a device to be connected to the adapter but not |
| // paired (example: a request for the adapter's SDP records). |
| if (!device || !(device->IsConnected() && device->IsPaired()) || |
| !base::Contains(device->GetUUIDs(), kMessageStreamUuid)) { |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ |
| << ": found connected device. Attempting to create " |
| "MessageStream for device = [" |
| << device->GetAddress() << "] " << device->GetNameForDisplay(); |
| AttemptCreateMessageStream(device->GetAddress(), |
| CreateMessageStreamAttemptType::kDeviceAdded); |
| } |
| |
| void MessageStreamLookupImpl::DeviceRemoved(device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device) { |
| if (!device) |
| return; |
| |
| // Remove message stream if the device removed from the adapter has a |
| // message stream and disconnect from socket if applicable. It isn't expected |
| // to already have a MessageStream associated with it. |
| AttemptEraseMessageStream(device->GetAddress()); |
| } |
| |
| void MessageStreamLookupImpl::AttemptRemoveMessageStream( |
| const std::string& device_address) { |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": device address = " << device_address; |
| AttemptEraseMessageStream(device_address); |
| } |
| |
| void MessageStreamLookupImpl::AttemptEraseMessageStream( |
| const std::string& device_address) { |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": device address = " << device_address; |
| // Remove map entry if it exists. It may not exist if it was failed to be |
| // created due to a |ConnectToService| error. |
| if (!base::Contains(message_streams_, device_address)) |
| return; |
| |
| // If the MessageStream still exists, we can attempt to gracefully disconnect |
| // the socket before erasing (and therefore destructing) the MessageStream |
| // instance. |
| message_streams_[device_address]->Disconnect( |
| base::BindOnce(&MessageStreamLookupImpl::OnSocketDisconnected, |
| weak_ptr_factory_.GetWeakPtr(), device_address)); |
| } |
| |
| void MessageStreamLookupImpl::OnSocketDisconnected( |
| const std::string& device_address) { |
| message_streams_.erase(device_address); |
| } |
| |
| void MessageStreamLookupImpl::AttemptCreateMessageStream( |
| const std::string& device_address, |
| const CreateMessageStreamAttemptType& type) { |
| device::BluetoothDevice* device = adapter_->GetDevice(device_address); |
| if (!device) { |
| CD_LOG(INFO, Feature::FP) |
| << __func__ << ": lost device for Message Stream creation"; |
| AttemptRemoveMessageStream(device_address); |
| return; |
| } |
| |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": device address = " << device_address |
| << " type = " << CreateMessageStreamAttemptTypeToString(type); |
| |
| // Only open MessageStreams for new devices that don't already have a |
| // MessageStream stored in the map. We can sometimes reach this point if |
| // multiple BluetoothAdapter events fire for a device connected event, but |
| // we need all of these BluetoothAdapter observation events to handle |
| // different connection scenarios, and have coverage for different devices. |
| if (base::Contains(message_streams_, device_address)) { |
| CD_LOG(INFO, Feature::FP) |
| << __func__ << ": Message Stream exists already for device"; |
| return; |
| } |
| |
| if (base::Contains(pending_connect_requests_, device_address)) { |
| CD_LOG(INFO, Feature::FP) |
| << __func__ << ": Ignoring due to matching pending request"; |
| return; |
| } |
| |
| pending_connect_requests_.insert(device_address); |
| |
| device->ConnectToService( |
| /*uuid=*/kMessageStreamUuid, /*callback=*/ |
| base::BindOnce(&MessageStreamLookupImpl::OnConnected, |
| weak_ptr_factory_.GetWeakPtr(), device_address, |
| base::TimeTicks::Now(), type), |
| /*error_callback=*/ |
| base::BindOnce(&MessageStreamLookupImpl::OnConnectError, |
| weak_ptr_factory_.GetWeakPtr(), device_address, type)); |
| } |
| |
| void MessageStreamLookupImpl::OnConnected( |
| std::string device_address, |
| base::TimeTicks connect_to_service_start_time, |
| const CreateMessageStreamAttemptType& type, |
| scoped_refptr<device::BluetoothSocket> socket) { |
| if (create_message_stream_retry_timers_.contains(device_address)) { |
| base::OneShotTimer* curr_create_message_stream_retry_timer = |
| create_message_stream_retry_timers_[device_address].get(); |
| |
| // This if branch should be unnecessary in theory, but it is included to |
| // address the edge case that a success occurs after a failure. |
| if (curr_create_message_stream_retry_timer->IsRunning()) |
| curr_create_message_stream_retry_timer->Stop(); |
| |
| size_t timer_erased_ct = |
| create_message_stream_retry_timers_.erase(device_address); |
| DCHECK(timer_erased_ct == 1); |
| size_t retry_ct_erased_ct = |
| create_message_stream_attempts_.erase(device_address); |
| DCHECK(retry_ct_erased_ct == 1); |
| } |
| |
| // It is expected that at the point of a successful RFCOMM connection, the |
| // device is known to the adapter. |
| device::BluetoothDevice* bt_device = adapter_->GetDevice(device_address); |
| DCHECK(bt_device); |
| CD_LOG(VERBOSE, Feature::FP) |
| << __func__ << ": device = " << device_address |
| << " device name = " << bt_device->GetNameForDisplay() |
| << " Type = " << CreateMessageStreamAttemptTypeToString(type); |
| |
| RecordMessageStreamConnectToServiceResult(/*success=*/true); |
| RecordMessageStreamConnectToServiceTime(base::TimeTicks::Now() - |
| connect_to_service_start_time); |
| |
| std::unique_ptr<MessageStream> message_stream = |
| std::make_unique<MessageStream>(device_address, std::move(socket)); |
| |
| for (auto& observer : observers_) |
| observer.OnMessageStreamConnected(device_address, message_stream.get()); |
| |
| message_streams_[device_address] = std::move(message_stream); |
| pending_connect_requests_.erase(device_address); |
| } |
| |
| void MessageStreamLookupImpl::OnConnectError( |
| std::string device_address, |
| const CreateMessageStreamAttemptType& type, |
| const std::string& error_message) { |
| // Because we need to attempt to create MessageStreams at many different |
| // iterations due to the variability of Bluetooth APIs, we can expect to |
| // see errors here frequently, along with errors followed by a success. |
| CD_LOG(INFO, Feature::FP) |
| << __func__ << ": Error: [ " << error_message |
| << "]. Type: " << CreateMessageStreamAttemptTypeToString(type) << "."; |
| RecordMessageStreamConnectToServiceResult(/*success=*/false); |
| RecordMessageStreamConnectToServiceError(error_message); |
| pending_connect_requests_.erase(device_address); |
| |
| // A timer is started to retry AttemptCreateMessageStream if |
| // the maximum number of attempts (6) to create the MessageStream has not been |
| // reached. If this is the first retry, new entries in |
| // |create_message_stream_attempts_| and |
| // |create_message_stream_retry_timers_| are created. |
| create_message_stream_attempts_.try_emplace(device_address, 1); |
| |
| int& create_message_stream_attempt_num = |
| create_message_stream_attempts_[device_address]; |
| if (create_message_stream_attempt_num == kMaxCreateMessageStreamAttempts) { |
| CD_LOG(INFO, Feature::FP) |
| << __func__ |
| << ": 6 attempts to create a message stream have failed. " |
| "There are no more retries."; |
| return; |
| } |
| |
| device::BluetoothDevice* device = adapter_->GetDevice(device_address); |
| if (device) { |
| create_message_stream_retry_timers_.try_emplace( |
| device_address, std::make_unique<base::OneShotTimer>()); |
| |
| base::OneShotTimer* curr_create_message_stream_retry_timer = |
| create_message_stream_retry_timers_[device_address].get(); |
| curr_create_message_stream_retry_timer->Start( |
| FROM_HERE, |
| kCreateMessageStreamRetryCooldowns[create_message_stream_attempt_num++ - |
| 1], |
| base::BindOnce(&MessageStreamLookupImpl::AttemptCreateMessageStream, |
| weak_ptr_factory_.GetWeakPtr(), device_address, type)); |
| } else { |
| CD_LOG(INFO, Feature::FP) |
| << __func__ << ": attempting to retry message stream creation with " |
| << " a device no longer found by the adapter." |
| << " device address: " << device_address; |
| size_t retry_ct_erased_ct = |
| create_message_stream_attempts_.erase(device_address); |
| DCHECK(retry_ct_erased_ct == 1); |
| create_message_stream_retry_timers_.erase(device_address); |
| } |
| } |
| |
| } // namespace quick_pair |
| } // namespace ash |