Skip to content

Commit

Permalink
[CrOS Bluetooth] Add DeviceCache
Browse files Browse the repository at this point in the history
This class is responsible for caching known paired Bluetooth devices so
that they can be returned by the CrosBluetoothConfig API. This class is
named DeviceCache instead of PairedDeviceCache because future CLs will
also utilize this class for the pairing flow, which deals with unpaired
devices.

This CL does not instantiate DeviceCache; a follow-up CL instantiates it
and uses it to return device metadata to clients.

Bug: 1010321
Change-Id: I9fdb72e776fa941533a5448721f4f01e3c693097
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3070213
Reviewed-by: Gordon Seto <gordonseto@google.com>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#909022}
  • Loading branch information
Kyle Horimoto authored and Chromium LUCI CQ committed Aug 5, 2021
1 parent 87544d9 commit 3cd0707
Show file tree
Hide file tree
Showing 9 changed files with 730 additions and 7 deletions.
7 changes: 7 additions & 0 deletions chromeos/services/bluetooth_config/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ static_library("bluetooth_config") {
"adapter_state_controller_impl.h",
"cros_bluetooth_config.cc",
"cros_bluetooth_config.h",
"device_cache.cc",
"device_cache.h",
"device_cache_impl.cc",
"device_cache_impl.h",
"device_conversion_util.cc",
"device_conversion_util.h",
"initializer.h",
Expand Down Expand Up @@ -58,6 +62,8 @@ static_library("test_support") {
sources = [
"fake_adapter_state_controller.cc",
"fake_adapter_state_controller.h",
"fake_device_cache.cc",
"fake_device_cache.h",
"fake_system_properties_observer.cc",
"fake_system_properties_observer.h",
"scoped_bluetooth_config_test_helper.cc",
Expand All @@ -80,6 +86,7 @@ source_set("unit_tests") {
sources = [
"adapter_state_controller_impl_unittest.cc",
"cros_bluetooth_config_unittest.cc",
"device_cache_impl_unittest.cc",
"device_conversion_util_unittest.cc",
"system_properties_provider_impl_unittest.cc",
]
Expand Down
51 changes: 51 additions & 0 deletions chromeos/services/bluetooth_config/device_cache.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2021 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 "chromeos/services/bluetooth_config/device_cache.h"

#include "chromeos/services/bluetooth_config/adapter_state_controller.h"

namespace chromeos {
namespace bluetooth_config {

DeviceCache::DeviceCache(AdapterStateController* adapter_state_controller)
: adapter_state_controller_(adapter_state_controller) {}

DeviceCache::~DeviceCache() = default;

std::vector<mojom::PairedBluetoothDevicePropertiesPtr>
DeviceCache::GetPairedDevices() const {
// If Bluetooth is not enabled or enabling, return an empty list. This
// addresses an edge case: when the user disables Bluetooth, there is a short
// amount of time in which Bluetooth is still enabled but is in the process of
// turning off. We should still return an empty list in this case to ensure
// that the UI does not show a list of devices when the toggle is off.
if (!IsBluetoothEnabledOrEnabling())
return {};

return PerformGetPairedDevices();
}

void DeviceCache::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}

void DeviceCache::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}

void DeviceCache::NotifyPairedDevicesListChanged() {
for (auto& observer : observers_)
observer.OnPairedDevicesListChanged();
}

bool DeviceCache::IsBluetoothEnabledOrEnabling() const {
const mojom::BluetoothSystemState adapter_state =
adapter_state_controller_->GetAdapterState();
return adapter_state == mojom::BluetoothSystemState::kEnabled ||
adapter_state == mojom::BluetoothSystemState::kEnabling;
}

} // namespace bluetooth_config
} // namespace chromeos
70 changes: 70 additions & 0 deletions chromeos/services/bluetooth_config/device_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2021 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.

#ifndef CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_H_
#define CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_H_

#include <vector>

#include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"

namespace chromeos {
namespace bluetooth_config {

class AdapterStateController;

// Caches known Bluetooth devices, providing getters and an observer interface
// for receiving updates when devices change.
//
// TODO(khorimoto): Also add support for tracking non-paired devices.
class DeviceCache {
public:
class Observer : public base::CheckedObserver {
public:
~Observer() override = default;

// Invoked when the list of paired devices has changed. This callback is
// used when a device has been added/removed from the list, or when one or
// more properties of a device in the list has changed.
virtual void OnPairedDevicesListChanged() = 0;
};

virtual ~DeviceCache();

// Returns a sorted list of all paired devices. The list is sorted such that
// connected devices appear before connecting devices, which appear before
// disconnected devices. If Bluetooth is disabled, disabling, or unavailable,
// this function returns an empty list.
std::vector<mojom::PairedBluetoothDevicePropertiesPtr> GetPairedDevices()
const;

void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);

protected:
DeviceCache(AdapterStateController* adapter_state_controller);

// Implementation-specific version of GetPairedDevices(), which is invoked
// only if Bluetooth is enabled or enabling.
virtual std::vector<mojom::PairedBluetoothDevicePropertiesPtr>
PerformGetPairedDevices() const = 0;

AdapterStateController* adapter_state_controller() {
return adapter_state_controller_;
}

void NotifyPairedDevicesListChanged();

private:
bool IsBluetoothEnabledOrEnabling() const;

AdapterStateController* adapter_state_controller_;

base::ObserverList<Observer> observers_;
};

} // namespace bluetooth_config
} // namespace chromeos

#endif // CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_H_
144 changes: 144 additions & 0 deletions chromeos/services/bluetooth_config/device_cache_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2021 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 "chromeos/services/bluetooth_config/device_cache_impl.h"

#include <algorithm>

#include "chromeos/services/bluetooth_config/device_conversion_util.h"

namespace chromeos {
namespace bluetooth_config {
namespace {

mojom::PairedBluetoothDevicePropertiesPtr
GeneratePairedBluetoothDeviceProperties(const device::BluetoothDevice* device) {
mojom::PairedBluetoothDevicePropertiesPtr properties =
mojom::PairedBluetoothDeviceProperties::New();
properties->device_properties = GenerateBluetoothDeviceMojoProperties(device);
// TODO(khorimoto): Add paired device nickname property.
return properties;
}

} // namespace

DeviceCacheImpl::DeviceCacheImpl(
AdapterStateController* adapter_state_controller_param,
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
: DeviceCache(adapter_state_controller_param),
bluetooth_adapter_(std::move(bluetooth_adapter)) {
adapter_state_controller_observation_.Observe(adapter_state_controller());
adapter_observation_.Observe(bluetooth_adapter_.get());

FetchInitialPairedDeviceList();
}

DeviceCacheImpl::~DeviceCacheImpl() = default;

std::vector<mojom::PairedBluetoothDevicePropertiesPtr>
DeviceCacheImpl::PerformGetPairedDevices() const {
std::vector<mojom::PairedBluetoothDevicePropertiesPtr> paired_devices;
for (const auto& paired_device : paired_devices_)
paired_devices.push_back(paired_device.Clone());
return paired_devices;
}

void DeviceCacheImpl::OnAdapterStateChanged() {
NotifyPairedDevicesListChanged();
}

void DeviceCacheImpl::DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
AttemptSetDeviceInPairedDeviceList(device);
}

void DeviceCacheImpl::DeviceRemoved(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
RemoveFromPairedDeviceList(device);
}

void DeviceCacheImpl::DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
AttemptUpdatePairedDeviceMetadata(device);
}

void DeviceCacheImpl::DevicePairedChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool new_paired_status) {
if (new_paired_status)
AttemptUpdatePairedDeviceMetadata(device);
else
RemoveFromPairedDeviceList(device);
}

void DeviceCacheImpl::DeviceConnectedStateChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool is_now_connected) {
AttemptUpdatePairedDeviceMetadata(device);
}

void DeviceCacheImpl::DeviceBatteryChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
absl::optional<uint8_t> new_battery_percentage) {
AttemptUpdatePairedDeviceMetadata(device);
}

void DeviceCacheImpl::FetchInitialPairedDeviceList() {
for (const device::BluetoothDevice* device :
bluetooth_adapter_->GetDevices()) {
paired_devices_.push_back(GeneratePairedBluetoothDeviceProperties(device));
}

SortPairedDeviceList();
}

void DeviceCacheImpl::AttemptSetDeviceInPairedDeviceList(
device::BluetoothDevice* device) {
if (!device->IsPaired())
return;

// Remove the old (stale) properties, if they exist.
RemoveFromPairedDeviceList(device);

paired_devices_.push_back(GeneratePairedBluetoothDeviceProperties(device));
SortPairedDeviceList();
NotifyPairedDevicesListChanged();
}

void DeviceCacheImpl::RemoveFromPairedDeviceList(
device::BluetoothDevice* device) {
auto it = paired_devices_.begin();
while (it != paired_devices_.end()) {
if (device->GetIdentifier() == (*it)->device_properties->id) {
paired_devices_.erase(it);
NotifyPairedDevicesListChanged();
return;
}

++it;
}
}

void DeviceCacheImpl::AttemptUpdatePairedDeviceMetadata(
device::BluetoothDevice* device) {
// Remove existing metadata about |device|.
RemoveFromPairedDeviceList(device);

// Now, add updated metadata.
AttemptSetDeviceInPairedDeviceList(device);
}

void DeviceCacheImpl::SortPairedDeviceList() {
std::sort(paired_devices_.begin(), paired_devices_.end(),
[](const mojom::PairedBluetoothDevicePropertiesPtr& first,
const mojom::PairedBluetoothDevicePropertiesPtr& second) {
return first->device_properties->connection_state >
second->device_properties->connection_state;
});
}

} // namespace bluetooth_config
} // namespace chromeos
98 changes: 98 additions & 0 deletions chromeos/services/bluetooth_config/device_cache_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2021 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.

#ifndef CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_IMPL_H_
#define CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_IMPL_H_

#include "base/memory/ref_counted.h"
#include "base/scoped_observation.h"
#include "chromeos/services/bluetooth_config/adapter_state_controller.h"
#include "chromeos/services/bluetooth_config/device_cache.h"
#include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "device/bluetooth/bluetooth_adapter.h"

namespace chromeos {
namespace bluetooth_config {

// Concrete DeviceCache implementation. When this class is created, it uses
// BluetoothAdapter to fetch an initial list of devices; then, it observes
// BluetoothAdapter so that it can update the cache when devices are added,
// removed, or changed.
//
// Additionally, it uses AdapterStateController to ensure that no devices are
// returned unless Bluetooth is enabled or enabling.
class DeviceCacheImpl : public DeviceCache,
public AdapterStateController::Observer,
public device::BluetoothAdapter::Observer {
public:
DeviceCacheImpl(AdapterStateController* adapter_state_controller,
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
~DeviceCacheImpl() override;

private:
friend class DeviceCacheImplTest;

// DeviceCache:
std::vector<mojom::PairedBluetoothDevicePropertiesPtr>
PerformGetPairedDevices() const override;

// AdapterStateController::Observer:
void OnAdapterStateChanged() override;

// device::BluetoothAdapter::Observer:
void DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) override;
void DeviceRemoved(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) override;
void DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) override;
void DevicePairedChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool new_paired_status) override;
void DeviceConnectedStateChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool is_now_connected) override;
void DeviceBatteryChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
absl::optional<uint8_t> new_battery_percentage) override;

// Fetches all known devices from BluetoothAdapter.
void FetchInitialPairedDeviceList();

// Sorts |paired_devices_| based on connection state. This function is called
// each time a device is added to the list. This is not particularly
// efficient, but the list is expected to be small and is only sorted when its
// contents change.
void SortPairedDeviceList();

// Adds |device| to |paired_devices_|, but only if |device| is paired. If the
// device was already present in the list, this function updates its metadata
// to reflect up-to-date values. This function sorts the list after a new
// element is inserted.
void AttemptSetDeviceInPairedDeviceList(device::BluetoothDevice* device);

// Removes |device| from |paired_devices_| if it exists in the list.
void RemoveFromPairedDeviceList(device::BluetoothDevice* device);

// Attempts to add updated metadata about |device| to |paired_devices_|.
void AttemptUpdatePairedDeviceMetadata(device::BluetoothDevice* device);

scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;

// Sorted by connection status.
std::vector<mojom::PairedBluetoothDevicePropertiesPtr> paired_devices_;

base::ScopedObservation<AdapterStateController,
AdapterStateController::Observer>
adapter_state_controller_observation_{this};
base::ScopedObservation<device::BluetoothAdapter,
device::BluetoothAdapter::Observer>
adapter_observation_{this};
};

} // namespace bluetooth_config
} // namespace chromeos

#endif // CHROMEOS_SERVICES_BLUETOOTH_CONFIG_DEVICE_CACHE_IMPL_H_

0 comments on commit 3cd0707

Please sign in to comment.