// 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 "chromeos/ash/services/bluetooth_config/device_cache_impl.h"
#include <memory>
#include <vector>
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/services/bluetooth_config/fake_adapter_state_controller.h"
#include "chromeos/ash/services/bluetooth_config/fake_device_name_manager.h"
#include "chromeos/ash/services/bluetooth_config/fake_fast_pair_delegate.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash::bluetooth_config {
namespace {
using PairedDeviceList = std::vector<mojom::PairedBluetoothDevicePropertiesPtr>;
using UnpairedDeviceList = std::vector<mojom::BluetoothDevicePropertiesPtr>;
using NiceMockDevice =
std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>;
const uint32_t kTestBluetoothClass = 1337u;
const char kTestBluetoothName[] = "testName";
const char kTestBluetoothNickname[] = "nickname";
constexpr char kTestDefaultImage[] = "data:image/png;base64,TestDefaultImage";
class FakeObserver : public DeviceCache::Observer {
public:
FakeObserver() = default;
~FakeObserver() override = default;
size_t num_paired_list_changed_calls() const {
return num_paired_list_changed_calls_;
}
size_t num_unpaired_list_changed_calls() const {
return num_unpaired_list_changed_calls_;
}
private:
// DeviceCache::Observer:
void OnPairedDevicesListChanged() override {
++num_paired_list_changed_calls_;
}
void OnUnpairedDevicesListChanged() override {
++num_unpaired_list_changed_calls_;
}
size_t num_paired_list_changed_calls_ = 0u;
size_t num_unpaired_list_changed_calls_ = 0u;
};
} // namespace
class DeviceCacheImplTest : public testing::Test {
protected:
DeviceCacheImplTest() = default;
DeviceCacheImplTest(const DeviceCacheImplTest&) = delete;
DeviceCacheImplTest& operator=(const DeviceCacheImplTest&) = delete;
~DeviceCacheImplTest() override = default;
// testing::Test:
void SetUp() override {
mock_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
ON_CALL(*mock_adapter_, GetDevices())
.WillByDefault(
testing::Invoke(this, &DeviceCacheImplTest::GenerateDevices));
}
void TearDown() override { device_cache_->RemoveObserver(&fake_observer_); }
void Init() {
device_cache_ = std::make_unique<DeviceCacheImpl>(
&fake_adapter_state_controller_, mock_adapter_,
&fake_device_name_manager_, &fake_fast_pair_delegate_);
device_cache_->AddObserver(&fake_observer_);
}
void SetBluetoothSystemState(mojom::BluetoothSystemState system_state) {
fake_adapter_state_controller_.SetSystemState(system_state);
}
void AddDevice(bool paired,
bool connected,
std::string* id_out,
const std::optional<int8_t> inquiry_rssi = std::nullopt,
const device::BluetoothDeviceType device_type =
device::BluetoothDeviceType::AUDIO,
DeviceImageInfo* image_info = nullptr) {
// We use the number of devices created in this test as the address.
std::string address = base::NumberToString(num_devices_created_);
++num_devices_created_;
// Mock devices have their ID set to "${address}-Identifier".
*id_out = base::StrCat({address, "-Identifier"});
auto mock_device =
std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
mock_adapter_.get(), kTestBluetoothClass, kTestBluetoothName,
address, paired, connected);
ON_CALL(*mock_device, GetType())
.WillByDefault(testing::Return(device::BLUETOOTH_TRANSPORT_DUAL));
ON_CALL(*mock_device, GetInquiryRSSI())
.WillByDefault(testing::Return(inquiry_rssi));
ON_CALL(*mock_device, GetDeviceType())
.WillByDefault(testing::Return(device_type));
if (image_info)
fake_fast_pair_delegate_.SetDeviceImageInfo(address, *image_info);
device::BluetoothDevice* device = mock_device.get();
mock_devices_.push_back(std::move(mock_device));
if (device_cache_)
device_cache_->DeviceAdded(mock_adapter_.get(), device);
}
void RemoveDevice(const std::string& device_id) {
auto it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
NiceMockDevice device = std::move(*it);
mock_devices_.erase(it);
device_cache_->DeviceRemoved(mock_adapter_.get(), device.get());
}
void ChangePairingState(const std::string& device_id, bool is_now_paired) {
auto it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
ON_CALL(**it, IsPaired()).WillByDefault(testing::Return(is_now_paired));
ON_CALL(**it, IsBonded()).WillByDefault(testing::Return(is_now_paired));
device_cache_->DevicePairedChanged(mock_adapter_.get(), it->get(),
is_now_paired);
}
void ChangeDeviceType(const std::string& device_id,
device::BluetoothDeviceType new_type) {
auto it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
ON_CALL(**it, GetDeviceType()).WillByDefault(testing::Return(new_type));
device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
}
void ChangeDeviceIsBlockedByPolicy(const std::string& device_id,
bool is_blocked_by_policy) {
std::vector<NiceMockDevice>::iterator it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
it->get()->SetIsBlockedByPolicy(is_blocked_by_policy);
}
void ChangeInquiryRssi(const std::string& device_id,
const std::optional<int8_t> inquiry_rssi) {
std::vector<NiceMockDevice>::iterator it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
ON_CALL(**it, GetInquiryRSSI())
.WillByDefault(testing::Return(inquiry_rssi));
device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
}
void SetDeviceNickname(const std::string& device_id,
const std::string& nickname) {
fake_device_name_manager_.SetDeviceNickname(device_id, nickname);
}
std::optional<std::string> GetDeviceNickname(std::string address) {
return fake_fast_pair_delegate_.GetDeviceNickname(address);
}
void ForgetDevice(const std::string& device_id) {
// Simulates the real-life behavior of when a device is forgotten.
auto it = FindDevice(device_id);
EXPECT_TRUE(it != mock_devices_.end());
// The device should start paired.
EXPECT_TRUE(it->get()->IsPaired());
// DevicePairedChanged() is called first.
device_cache_->DevicePairedChanged(mock_adapter_.get(), it->get(),
/*new_paired_status=*/false);
// DeviceChanged() gets called twice with device->IsPaired() still true.
device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
// The device is then removed.
NiceMockDevice device = std::move(*it);
mock_devices_.erase(it);
device_cache_->DeviceRemoved(mock_adapter_.get(), device.get());
}
PairedDeviceList GetPairedDevices() {
return device_cache_->GetPairedDevices();
}
UnpairedDeviceList GetUnpairedDevices() {
return device_cache_->GetUnpairedDevices();
}
size_t GetNumPairedDeviceListObserverEvents() const {
return fake_observer_.num_paired_list_changed_calls();
}
size_t GetNumUnpairedDeviceListObserverEvents() const {
return fake_observer_.num_unpaired_list_changed_calls();
}
private:
std::vector<raw_ptr<const device::BluetoothDevice, VectorExperimental>>
GenerateDevices() {
std::vector<raw_ptr<const device::BluetoothDevice, VectorExperimental>>
devices;
for (auto& device : mock_devices_)
devices.push_back(device.get());
return devices;
}
std::vector<NiceMockDevice>::iterator FindDevice(
const std::string& device_id) {
return base::ranges::find(
mock_devices_, device_id,
&testing::NiceMock<device::MockBluetoothDevice>::GetIdentifier);
}
base::test::TaskEnvironment task_environment_;
std::vector<NiceMockDevice> mock_devices_;
size_t num_devices_created_ = 0u;
FakeAdapterStateController fake_adapter_state_controller_;
FakeDeviceNameManager fake_device_name_manager_;
FakeFastPairDelegate fake_fast_pair_delegate_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
FakeObserver fake_observer_;
std::unique_ptr<DeviceCacheImpl> device_cache_;
};
TEST_F(DeviceCacheImplTest, AddAndRemovePairedDevices) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
// Add device 1 (disconnected).
std::string paired_device_id1;
AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id1);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id1, list[0]->device_properties->id);
// Add device 2 (connected). Paired connected devices should be returned
// before disconnected ones.
std::string paired_device_id2;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id2);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(2u, list.size());
EXPECT_EQ(paired_device_id2, list[0]->device_properties->id);
EXPECT_EQ(paired_device_id1, list[1]->device_properties->id);
// Remove device 2; only device 1 should be returned.
RemoveDevice(paired_device_id2);
EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id1, list[0]->device_properties->id);
}
TEST_F(DeviceCacheImplTest, AddAndRemoveUnpairedDevices) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add device 1.
std::string unpaired_device_id1;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id1,
/*inquiry_rssi=*/1);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList list = GetUnpairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(unpaired_device_id1, list[0]->id);
// Add device 2 with higher signal strength than device 1. Device 2 should be
// returned first.
std::string unpaired_device_id2;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id2,
/*inquiry_rssi=*/2);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
list = GetUnpairedDevices();
EXPECT_EQ(2u, list.size());
EXPECT_EQ(unpaired_device_id2, list[0]->id);
EXPECT_EQ(unpaired_device_id1, list[1]->id);
// Remove device 2; only device 1 should be returned.
RemoveDevice(unpaired_device_id2);
EXPECT_EQ(3u, GetNumUnpairedDeviceListObserverEvents());
list = GetUnpairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(unpaired_device_id1, list[0]->id);
}
TEST_F(DeviceCacheImplTest, AddBeforeInit) {
// Add device 1, a paired device, and device 2, an unpaired device before
// initializing the class.
std::string paired_device_id1;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id1);
std::string unpaired_device_id2;
AddDevice(/*paired=*/false, /*connected=*/true, &unpaired_device_id2);
Init();
// Device 1 should be available in the paired device list from the getgo,
// device 2 should not be present.
PairedDeviceList paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id1, paired_list[0]->device_properties->id);
// Device 2 should be available in the unpaired device list from the getgo,
// device 1 should not be present.
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id2, unpaired_list[0]->id);
// Add paired device 3 and verify that device 1 and device 3 are returned in
// the paired device list.
std::string paired_device_id3;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id3);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
paired_list = GetPairedDevices();
EXPECT_EQ(2u, paired_list.size());
EXPECT_EQ(paired_device_id1, paired_list[0]->device_properties->id);
EXPECT_EQ(paired_device_id3, paired_list[1]->device_properties->id);
// Add unpaired device 4 and verify that device 2 and device 4 are returned in
// the unpaired device list.
std::string unpaired_device_id4;
AddDevice(/*paired=*/false, /*connected=*/true, &unpaired_device_id4);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
unpaired_list = GetUnpairedDevices();
EXPECT_EQ(2u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id2, unpaired_list[0]->id);
EXPECT_EQ(unpaired_device_id4, unpaired_list[1]->id);
}
TEST_F(DeviceCacheImplTest, AddPairedAndUnpairedDevices) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id, paired_list[0]->device_properties->id);
// Unpaired device observer should not be notified and unpaired device list
// should still be empty.
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add an unpaired device.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/true, &unpaired_device_id);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id);
// Paired device observer should not be notified and paired device list should
// still only have one element.
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id, paired_list[0]->device_properties->id);
}
TEST_F(DeviceCacheImplTest, PairingStateChanges) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_TRUE(GetUnpairedDevices().empty());
// Unpair; the paired list observer should be notified and the device should
// not be returned in the paired list.
ChangePairingState(paired_device_id, /*is_now_paired=*/false);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
EXPECT_TRUE(GetPairedDevices().empty());
// The unpaired list observer should also be notified and the device should be
// returned in the unpaired list.
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(paired_device_id, unpaired_list[0]->id);
// Re-pair; the paired list observer should be notified, and the device should
// be returned in the paired list.
ChangePairingState(paired_device_id, /*is_now_paired=*/true);
EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
// The unpaired list observer should also be notified and the device should
// not be returned in the unpaired list.
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, PairedDeviceNicknameChanges) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_FALSE(list[0]->nickname);
// Set the device's nickname
SetDeviceNickname(paired_device_id, kTestBluetoothNickname);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
// In this test file, AddDevice() uses |num_devices_created_| converted to a
// string as the address for each device. Only one device is created in this
// test so "0" is the address of the device we want to check.
auto nickname = GetDeviceNickname("0");
EXPECT_TRUE(nickname.has_value());
EXPECT_TRUE(nickname.value() == kTestBluetoothNickname);
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_EQ(kTestBluetoothNickname, list[0]->nickname);
}
TEST_F(DeviceCacheImplTest, PairedDeviceAdminPolicyChanges) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_FALSE(list[0]->device_properties->is_blocked_by_policy);
// Change its admin policy.
ChangeDeviceIsBlockedByPolicy(paired_device_id,
/*is_blocked_by_policy=*/true);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_TRUE(list[0]->device_properties->is_blocked_by_policy);
}
TEST_F(DeviceCacheImplTest, PairedDeviceBluetoothClassChanges) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_EQ(mojom::DeviceType::kHeadset,
list[0]->device_properties->device_type);
// Change its device type to a different supported type.
ChangeDeviceType(paired_device_id, device::BluetoothDeviceType::AUDIO);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
EXPECT_EQ(mojom::DeviceType::kHeadset,
list[0]->device_properties->device_type);
// Change its device type to an unsupported type. Bonded devices are expected
// to remain in the list even if they have an unsupported type.
ChangeDeviceType(paired_device_id, device::BluetoothDeviceType::PHONE);
EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
}
TEST_F(DeviceCacheImplTest, PairedDeviceForgotten) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
// Add a paired device.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList list = GetPairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
ForgetDevice(paired_device_id);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
EXPECT_TRUE(GetPairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, UnpairedDeviceBluetoothClassChanges) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a unpaired device.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/true, &unpaired_device_id);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList list = GetUnpairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(unpaired_device_id, list[0]->id);
EXPECT_EQ(mojom::DeviceType::kHeadset, list[0]->device_type);
// Change its device type.
ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
list = GetUnpairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(unpaired_device_id, list[0]->id);
EXPECT_EQ(mojom::DeviceType::kVideoCamera, list[0]->device_type);
}
TEST_F(DeviceCacheImplTest, UnpairedDeviceSignalStrengthChanges) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add device 1.
std::string unpaired_device_id1;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id1,
/*inquiry_rssi=*/1);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList list = GetUnpairedDevices();
EXPECT_EQ(1u, list.size());
EXPECT_EQ(unpaired_device_id1, list[0]->id);
// Add device 2 with higher signal strength than device 1. Device 2 should be
// returned first.
std::string unpaired_device_id2;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id2,
/*inquiry_rssi=*/2);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
list = GetUnpairedDevices();
EXPECT_EQ(2u, list.size());
EXPECT_EQ(unpaired_device_id2, list[0]->id);
EXPECT_EQ(unpaired_device_id1, list[1]->id);
// Update device 1's signal strength to be greater than device 2. Device 1
// should now be returned first.
ChangeInquiryRssi(unpaired_device_id1, 3);
EXPECT_EQ(3u, GetNumUnpairedDeviceListObserverEvents());
list = GetUnpairedDevices();
EXPECT_EQ(2u, list.size());
EXPECT_EQ(unpaired_device_id1, list[0]->id);
EXPECT_EQ(unpaired_device_id2, list[1]->id);
}
TEST_F(DeviceCacheImplTest, BluetoothTurnsOff) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a paired device and an unpaired device.
std::string paired_device_id1;
AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id1);
std::string unpaired_device_id2;
AddDevice(/*paired=*/false, /*connected=*/true, &unpaired_device_id2);
// Both devices should be present in their respective lists.
PairedDeviceList paired_list = GetPairedDevices();
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id1, paired_list[0]->device_properties->id);
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id2, unpaired_list[0]->id);
// Turn off Bluetooth.
SetBluetoothSystemState(mojom::BluetoothSystemState::kDisabling);
// Observers for both lists should be notified and empty lists returned.
paired_list = GetPairedDevices();
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
EXPECT_TRUE(GetPairedDevices().empty());
unpaired_list = GetUnpairedDevices();
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceNotReturned) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add an unknown device. This should not be added to the unpaired list and no
// observers notified.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::UNKNOWN);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Update a property in the device. This should not cause any observable
// actions.
ChangeInquiryRssi(unpaired_device_id, 3);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Remove the device. This should not cause any observable actions.
RemoveDevice(unpaired_device_id);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, UnsupportedUnpairedDeviceNotReturned) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a device of type PHONE. This should not be added to the unpaired list
// and no observers notified because this device type is unsupported.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::PHONE);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Update a property in the device. This should not cause any observable
// actions.
ChangeInquiryRssi(unpaired_device_id, 3);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Remove the device. This should not cause any observable actions.
RemoveDevice(unpaired_device_id);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceChangesToKnown) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add an unknown device. This should not be added to the unpaired list and no
// observers notified.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::UNKNOWN);
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Update the device type to a known type. This should add the device to the
// unpaired list.
ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO);
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id);
// Remove the device. This should notify observers.
RemoveDevice(unpaired_device_id);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, KnownUnpairedDeviceChangesToUnknown) {
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add a known device. This should notify observers.
std::string unpaired_device_id;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::VIDEO);
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id);
// Update the device type to unknown type. This should remove the device from
// the unpaired list and notify observers.
ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::UNKNOWN);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Remove the device. This should not cause any observable actions.
RemoveDevice(unpaired_device_id);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, UnknownPairedDeviceReturned) {
Init();
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_TRUE(GetUnpairedDevices().empty());
// Add an unknown paired device. This should notify observers.
std::string paired_device_id;
AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::UNKNOWN);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id, paired_list[0]->device_properties->id);
// Update a property in the device. This should notify observers.
ChangeDeviceIsBlockedByPolicy(paired_device_id,
/*is_blocked_by_policy=*/true);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_TRUE(paired_list[0]->device_properties->is_blocked_by_policy);
// Change the device to unpaired. This should update the paired device list
// but not the unpaired device list.
ChangePairingState(paired_device_id, /*is_now_paired=*/false);
EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
EXPECT_TRUE(GetUnpairedDevices().empty());
}
TEST_F(DeviceCacheImplTest, PairedDeviceImageInfo) {
// Add a paired device with image info before initializing.
std::string paired_device_id1;
DeviceImageInfo image_info = DeviceImageInfo(
/*default_image=*/kTestDefaultImage, /*left_bud_image=*/"",
/*right_bud_image=*/"", /*case_image=*/"");
AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id1,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO, &image_info);
Init();
EXPECT_TRUE(GetUnpairedDevices().empty());
EXPECT_EQ(0u, GetNumPairedDeviceListObserverEvents());
PairedDeviceList paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id1, paired_list[0]->device_properties->id);
EXPECT_TRUE(paired_list[0]->device_properties->image_info);
RemoveDevice(paired_device_id1);
EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
// Add a paired device without image info. This should notify observers.
std::string paired_device_id2;
AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id2,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO);
EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id2, paired_list[0]->device_properties->id);
EXPECT_FALSE(paired_list[0]->device_properties->image_info);
RemoveDevice(paired_device_id2);
EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
// Add a paired device with image info. This should notify observers.
std::string paired_device_id3;
AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id3,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO, &image_info);
EXPECT_EQ(4u, GetNumPairedDeviceListObserverEvents());
paired_list = GetPairedDevices();
EXPECT_EQ(1u, paired_list.size());
EXPECT_EQ(paired_device_id3, paired_list[0]->device_properties->id);
EXPECT_TRUE(paired_list[0]->device_properties->image_info);
}
TEST_F(DeviceCacheImplTest, UnpairedDeviceImageInfo) {
// Add an unpaired device with image info before initializing.
std::string unpaired_device_id1;
DeviceImageInfo image_info = DeviceImageInfo(
/*default_image=*/kTestDefaultImage, /*left_bud_image=*/"",
/*right_bud_image=*/"", /*case_image=*/"");
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id1,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO, &image_info);
Init();
EXPECT_TRUE(GetPairedDevices().empty());
EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
UnpairedDeviceList unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id1, unpaired_list[0]->id);
EXPECT_TRUE(unpaired_list[0]->image_info);
RemoveDevice(unpaired_device_id1);
EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
// Add an unpaired device without image info. This should notify observers.
std::string unpaired_device_id2;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id2,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO);
EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id2, unpaired_list[0]->id);
EXPECT_FALSE(unpaired_list[0]->image_info);
RemoveDevice(unpaired_device_id2);
EXPECT_EQ(3u, GetNumUnpairedDeviceListObserverEvents());
// Add an unpaired device with image info. This should notify observers.
std::string unpaired_device_id3;
AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id3,
/*inquiry_rssi=*/1,
/*device_type=*/device::BluetoothDeviceType::AUDIO, &image_info);
EXPECT_EQ(4u, GetNumUnpairedDeviceListObserverEvents());
unpaired_list = GetUnpairedDevices();
EXPECT_EQ(1u, unpaired_list.size());
EXPECT_EQ(unpaired_device_id3, unpaired_list[0]->id);
EXPECT_TRUE(unpaired_list[0]->image_info);
}
} // namespace ash::bluetooth_config