chromeos/ash/services/bluetooth_config/README.md
CrosBluetoothConfig
is a high-level Bluetooth API implemented using Mojo
and is the primary Bluetooth API used by clients within Chrome.
This document describes various details of the API such as what it does, how the API can be used, what the primary clients of the API are, and what its dependencies are.
Within Chrome, Bluetooth functionality is exposed via
//device/bluetooth
(see README).
The entrypoint to this library is BluetoothAdapterFactory,
which exposes an API for interacting with the BluetoothAdapter.
On ChromeOS, BluetoothAdapter utilizes FlossDBusManager
or BluezDBusManager,
depending on whether Floss or BlueZ is being used, to communicate with the
Bluetooth stack library via D-Bus. //device/bluetooth is not specific to
ChromeOS, and a lot of it is shared with other platforms and products (e.g.,
Chrome Browser).
BluetoothAdapter exposes APIs for powering Bluetooth on and off, discovering,
pairing and connecting to devices, and sending/receiving information from
these devices.
Because Chromium supports other operating systems, the BluetoothAdapter class
and the interfaces it exposes are intentionally generic and use different
implementations depending on the platform. Thus, in order to implement
Bluetooth UI in ChromeOS, there is additional logic wrapping.
There are four top-level UI surfaces used for ChromeOS Bluetooth:
The diagram above shows a simplified view of these relationships, in which
these all use CrosBluetoothConfig.
The CrosBluetoothConfig API does not contain all Bluetooth functionality in
ChromeOS. Specifically, some ChromeOS features (e.g., Nearby Share, Phone Hub,
Instant Tethering, Smart Lock) utilize lower-level Bluetooth APIs which are not
directly exposed by the UI. However, most Chrome clients should default to
using CrosBluetoothConfig unless specific, lower-level functionality is
required.
Currently in ChromeOS, there are two Bluetooth stacks: BlueZ and Floss. BlueZ is the legacy stack that is being replaced by Floss (as of Q4 2023). For more information see here.
CrosBluetoothConfig functionality resides in
//chromeos/ash/services/bluetooth_config/.
Mojo interfaces are defined within a
public/mojom
subdirectory. The primary interface CrosBluetoothConfig has a concrete
implementation within the top-level directory, which routes public API calls to
the internal classes it's composed of.
CrosBluetoothConfig's dependencies
are various lower-level Bluetooth classes such as BluetoothAdapter and
BluetoothDevice, UserManager, system prefs (both device-wide
"local" prefs, and per-profile prefs belonging to the primary login) and
FastPairDelegate.
The following APIs can be used by clients for observing and modifying the Bluetooth state:
// Notifies observer with initial set of Bluetooth properties when observer
// is first added, then again whenever properties are updated.
ObserveSystemProperties(pending_remote observer);
// Informs observer when a device is newly paired, connected or
// disconnected.
ObserveDeviceStatusChanges(
pending_remote<BluetoothDeviceStatusObserver> observer);
// Turns Bluetooth on or off.
SetBluetoothEnabledState(bool enabled);
Clients that wish to fetch the Bluetooth adapter's power state or the paired
device list should use
ObserveSystemProperties().
To update the adapter's power state clients should use
SetBluetoothEnabledState().
Unpaired Bluetooth devices are represented as BluetoothDeviceProperties, and
paired Bluetooth devices are represented as PairedBluetoothDeviceProperties
which is composed of BluetoothDeviceProperties and additional paired-specific
properties. Device information such as address, type, and battery level can be
accessed through these properties.
// Properties belonging to a Bluetooth device.
struct BluetoothDeviceProperties {
// Unique identifier for this device, which is stable across device reboots.
string id;
// The Bluetooth address of this device.
string address;
// Publicly-visible name provided by the device. If no name is provided by
// the device, the device address is used as a name.
mojo_base.mojom.String16 public_name;
// Device type, derived from the ClassOfDevice attribute for the device.
DeviceType device_type;
...
};
// Properties belonging to a Bluetooth device which has been paired to this
// Chrome OS device.
struct PairedBluetoothDeviceProperties {
BluetoothDeviceProperties device_properties;
// Nickname for this device as provided by the user. Local to the device
// (i.e., other devices do not have access to this name). Null if the
// device has not been nicknamed by the user.
string? nickname;
...
};
To pair with a device, clients should first start a discovery session to scan for Bluetooth devices:
// Starts a discovery session, during which time it will be possible
// to find new devices and pair them.
StartDiscovery(pending_remote<BluetoothDiscoveryDelegate> delegate);
Once discovery has started, clients should use the
DevicePairingHandler
to initiate pairing with a device:
interface BluetoothDiscoveryDelegate {
// Invoked when discovery has started.
// |handler| can be used to initiate pairing to a discovered device.
OnBluetoothDiscoveryStarted(
pending_remote<DevicePairingHandler> handler);
};
// Handles requests to pair to a device.
interface DevicePairingHandler {
// Attempts to pair to the device with ID |device_id|. Pairing often
// requires additional interaction from the user, so callers must
// provide a |delegate| which handles requests for these interactions.
// For example, pairing a Bluetooth keyboard usually requires that
// users type in a PIN.
//
// |result| is returned when the pairing attempt completes. It is
// possible that |result| is returned before any delegate function
// is invoked.
PairDevice(
string device_id,
pending_remote<DevicePairingDelegate> delegate) =>
(PairingResult result);
};
Pairing may require additional authentication. Clients who call pair device
should provide a DevicePairingDelegate which implements the handling of
different authentication scenarios that could possibly be required:
// Provided by the pairing UI to handle pairing requests of
// different types.
interface DevicePairingDelegate {
// Requests that a PIN be provided to complete pairing.
RequestPinCode() => (string pin_code);
// Requests that a passkey be provided to complete pairing.
RequestPasskey() => (string passkey);
// Requests that |pin_code| be displayed to the user, who should
// enter the PIN via a Bluetooth keyboard.
DisplayPinCode(string pin_code,
pending_receiver<KeyEnteredHandler> handler);
// Requests that |passkey| be displayed to the user, who should
// enter the passkey via a Bluetooth keyboard.
DisplayPasskey(string passkey,
pending_receiver<KeyEnteredHandler> handler);
// Requests that |passkey| be displayed to the user, who should
// confirm or reject a pairing request. Returns whether or not the
// user confirmed the passkey.
ConfirmPasskey(string passkey) => (bool confirmed);
// Requests that the user is asked to confirm or reject a pairing
// request. Returns whether or not the user confirmed the pairing.
AuthorizePairing() => (bool confirmed);
};
CrosBluetoothConfig exposes APIs for operations on paired Bluetooth devices,
such as connecting or forgetting, with the following APIs:
// Initiates a connection to the device with ID |device_id|.
Connect(string device_id) => (bool success);
// Initiates a disconnection from the device with ID |device_id|.
Disconnect(string device_id) => (bool success);
// Forgets the device with ID |device_id|, which in practice means
// un-pairing from the device.
Forget(string device_id) => (bool success);
Clients that use CrosBluetoothConfig can utilize a fake implementation of the
API by initializing CrosBluetoothConfig with
ScopedBluetoothConfigTestHelper
rather than
InitializerImpl.
CrosBluetoothConfig is composed of many internal classes which implement
specific functionality of the API. Some notable ones are:
SystemPropertiesProvider:
Provides system Bluetooth properties, including Bluetooth availability and
on/off stateAdapterStateController:
Handles retrieving and modifying the Bluetooth adapter powered stateDeviceCache:
Maintains the list of paired and unpaired Bluetooth devicesDiscoverySessionManager:
Starts and stops Bluetooth device discovery sessionsDevicePairingHandler:
Executes pairing attempts and manages pairing authentication if requiredDeviceOperationHandler:
Executes operations on paired devicesEach of these classes provides a fake implementation that can be used standalone
or with CrosBluetoothConfig. New functionality should either
be implemented in one of the internal classes, or if an entirely new class must
be created, a fake implementation should also be created for it so it can be
used for unit testing.
CrosBluetoothConfig has a concrete implementation
which routes public API calls to the internal classes it's composed of.
SystemPropertiesProviderThis class computes the current BluetoothSystemProperties.
These system properties provide information on the Bluetooth adapter state,
whether Bluetooth can be modified, and the list of paired devices. It provides
an observer method which clients can be used to be notified of system changes.
// Adds an observer of system properties. |observer| will be notified of
// the current properties immediately as a result of this function, then again
// each time system properties change. To stop observing, clients should
// disconnect the Mojo pipe to |observer| by deleting the associated Receiver.
void Observe(mojo::PendingRemote<mojom::SystemPropertiesObserver> observer);
AdapterStateControllerThis class controls the state of the Bluetooth adapter and serves as the source
of truth for the adapter's current state. This class modifies the Bluetooth
adapter directly and should only be used by classes that do not wish to persist
the adapter state to prefs. For classes that do wish to persist the adapter
state to prefs, such as those processing incoming user requests,
BluetoothPowerController
should be used instead.
Clients can call SetBluetoothEnabledState() to update the adapter state, and
can implement the Observer to be notified of adapter state changes:
class Observer : public base::CheckedObserver {
// Invoked when the state has changed; use GetAdapterState() to retrieve the
// updated state.
virtual void OnAdapterStateChanged() = 0;
};
// Returns the system state as obtained from the Bluetooth adapter.
virtual mojom::BluetoothSystemState GetAdapterState() const = 0;
// Turns Bluetooth on or off. If Bluetooth is unavailable or already in the
// desired state, this function is a no-op.
// This does not save to |enabled| to prefs. If |enabled| is wished to be
// saved to prefs, BluetoothPowerController::SetBluetoothEnabledState() should
// be used instead.
virtual void SetBluetoothEnabledState(bool enabled) = 0;
The implementation of this class is AdapterStateControllerImpl.
This class queues adapter state change requests to ensure only one
BluetoothAdapter::SetPowered() call is invoked at a time. It maintains the
operation being executed and one queued operation, and if another operation
comes in, the queued operation is overwritten.
BluetoothPowerControllerThis class sets the Bluetooth power state and saves the state to prefs. It also initializes the Bluetooth power state during system startup and user session startup.
Classes that wish to set the Bluetooth adapter state and save that value to
prefs should use this class. Classes that do not want to persist the state to
prefs should use AdapterStateController instead. Internally, this class
serves as a wrapper around AdapterStateController.
// Changes the Bluetooth power setting to |enabled|, persisting |enabled| to
// user prefs if a user is logged in. If no user is logged in, the pref is
// persisted to local state.
virtual void SetBluetoothEnabledState(bool enabled) = 0;
// Enables Bluetooth but doesn't persist the state to prefs. This should be
// called to enable Bluetooth when OOBE HID detection starts.
virtual void SetBluetoothHidDetectionActive() = 0;
// If |is_using_bluetooth| is false, restores the Bluetooth enabled state that
// was last persisted to local state. This should be called when OOBE HID
// detection ends.
virtual void SetBluetoothHidDetectionInactive(bool is_using_bluetooth) = 0;
DeviceCacheThis class caches known Bluetooth devices, providing getters and an observer interface for receiving updates when devices change. Classes can use this to get the list of paired and unpaired devices and be notified when items in these lists change.
class Observer : public base::CheckedObserver {
// 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() {}
// Invoked when the list of unpaired 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 OnUnpairedDevicesListChanged() {}
};
// 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;
// Returns a sorted list of unpaired devices. This list is sorted by signal
// strength.
std::vector<mojom::BluetoothDevicePropertiesPtr> GetUnpairedDevices() const;
This class is implemented at DeviceCacheImpl.
Using BluetoothAdapter observers methods, it maintains in-memory lists of
paired and unpaired devices.
DiscoverySessionManagerWhen unpaired devices wish to be found, a BluetoothAdapter discovery session
must be started. This class handles requests to start discovery sessions.
Clients invoke StartDiscovery() to begin the flow and disconnect the delegate
passed to StartDiscovery() to end the flow.
// Starts a discovery attempt. |delegate| is notified when the discovery
// session has started and stopped. To cancel a discovery attempt, disconnect
// |delegate|.
void StartDiscovery(
mojo::PendingRemote<mojom::BluetoothDiscoveryDelegate> delegate);
When a discovery session is started, observers of DeviceCache are immediately
informed of the current discovered Bluetooth devices and will continue to
receive updates whenever a device is added, updated or removed. Once the
session has ended, clients will no longer receive updates.
This class is implemented at DiscoverySessionManagerImpl.
Internally, this class ensures that Bluetooth discovery remains active as long
as at least one discovery client is active.
DiscoveredDevicesProviderClients that start a discovery session via DiscoverySessionManager can
observe DeviceCache in order to be notified of when unpaired devices are
discovered. However, for UI surfaces, updating each time the list changes can
provide an undesired UX behavior where the list is changing too rapidly.
DiscoveredDevicesProvider is a wrapper for DeviceCache that batches
unpaired devices list updates. If the device list has changed, this
implementation waits kNotificationDelay
before sorting and notifying clients that the list has changed. This is to
reduce the frequency of changes to the device list in UI surfaces, giving users
more time to view the list between updates.
class Observer : public base::CheckedObserver {
// Invoked when the list of discovered 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 OnDiscoveredDevicesListChanged() = 0;
};
When the list of unpaired devices changes, this method implements the following logic in effort to limit the frequency of device position changes clients observe:
DevicePairingHandlerThis class handles requests to pair to a Bluetooth device. This handler can be
reused to pair to more than one device. Only one device should be attempted to
be paired to at a time. Callees must pass in a DevicePairingDelegate to
handle potential authentication being required. If the delegate is
disconnected, any in-progress pairing is canceled.
void PairDevice(const std::string& device_id,
mojo::PendingRemote<mojom::DevicePairingDelegate> delegate,
PairDeviceCallback callback) override;
This class is implemented at DevicePairingHandlerImpl,
which interacts with the BluetoothDevice, serving as the device's
PairingDelegate, and relays the PairingDelegate method calls back to the
client that initiated the pairing request via the request's
DevicePairingDelegate.
DeviceOperationHandlerThis class provides operations that can be performed on paired devices, such as connecting or disconnecting to a device. Operations are performed sequentially, queueing requests that occur simultaneously.
// Initiates a connection to the device with ID |device_id|.
void Connect(const std::string& device_id, OperationCallback callback);
// Initiates a disconnection from the device with ID |device_id|.
void Disconnect(const std::string& device_id, OperationCallback callback);
// Forgets the device with ID |device_id|, which in practice means
// un-pairing from the device.
void Forget(const std::string& device_id, OperationCallback callback);
Operations have a timeout,
which when exceeded, returns to the client a failure result and the next
operation is processed. This class is implemented at
DeviceOperationHandlerImpl
which interfaces with the BluetoothDevice APIs.
BluetoothDeviceStatusNotifierThis class manages notifying listeners of changes in individual Bluetooth devices status. Status changes includes a newly paired device, new connection and new disconnection (which includes forgetting a connected device).
// Adds an observer of Bluetooth device status. |observer| will be notified
// each time Bluetooth device status changes. To stop observing, clients
// should disconnect the Mojo pipe to |observer| by deleting the associated
// Receiver.
void ObserveDeviceStatusChanges(
mojo::PendingRemote<mojom::BluetoothDeviceStatusObserver> observer);
This class is implemented at BluetoothDevicesStatusNotifierImpl,
which observes DeviceCache under the hood to compute changes to device
statuses.
DeviceNameManagerThis class manages saving and retrieving nicknames for Bluetooth devices. This nickname is local to only the Chromebook and is visible to all users of the Chromebook.
class Observer : public base::CheckedObserver {
// Invoked when the nickname of device with id |device_id| has changed to
// |nickname|. If |nickname| is null, the nickname has been removed for
// |device_id|.
virtual void OnDeviceNicknameChanged(
const std::string& device_id,
const std::optional<std::string>& nickname) = 0;
};
// Retrieves the nickname of the Bluetooth device with ID |device_id| or
// abs::nullopt if not found.
virtual std::optional<std::string> GetDeviceNickname(
const std::string& device_id) = 0;
// Sets the nickname of the Bluetooth device with ID |device_id| for all users
// of the current device, if |nickname| is valid.
virtual void SetDeviceNickname(const std::string& device_id,
const std::string& nickname) = 0;
// Removes the nickname of the Bluetooth device with ID |device_id| for all
// users of the current device.
virtual void RemoveDeviceNickname(const std::string& device_id) = 0;
This class is implemented at DeviceNameManagerImpl,
which saves entries to Prefs.