// 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