Commit e0fa0d30 authored by Giovanni Ortuño Urquidi's avatar Giovanni Ortuño Urquidi Committed by Commit Bot

bluetooth: Implement GetAvailableDevices

GetAvailableDevices returns devices we consider to be available i.e.
devices we're recently seen, paired devices, and connected devices.

Bug: 870192
Change-Id: I0076003ee9b47e9d828c30c4aca6caf6200896ec
Reviewed-on: https://chromium-review.googlesource.com/c/1359834Reviewed-by: default avatarTetsui Ohkubo <tetsui@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarSonny Sasaka <sonnysasaka@chromium.org>
Commit-Queue: Giovanni Ortuño Urquidi <ortuno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615755}
parent 7fe36f50
......@@ -77,9 +77,7 @@ bool TrayBluetoothHelperExperimental::HasBluetoothDiscoverySession() {
void TrayBluetoothHelperExperimental::GetBluetoothDevices(
GetBluetoothDevicesCallback callback) const {
NOTIMPLEMENTED();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), BluetoothDeviceList()));
bluetooth_system_ptr_->GetAvailableDevices(std::move(callback));
}
void TrayBluetoothHelperExperimental::OnStateChanged(
......
......@@ -4,19 +4,61 @@
#include "services/device/bluetooth/bluetooth_system.h"
#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "dbus/object_path.h"
#include "device/bluetooth/dbus/bluetooth_adapter_client.h"
#include "device/bluetooth/dbus/bluetooth_device_client.h"
#include "device/bluetooth/dbus/bluez_dbus_manager.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
namespace device {
namespace {
base::Optional<std::array<uint8_t, 6>> ParseAddress(
const std::string& address_str) {
// First check the str has the expected format.
static constexpr size_t kCanonicalAddressLength = 17;
if (address_str.size() != kCanonicalAddressLength)
return base::nullopt;
for (size_t i = 0; i < address_str.size(); ++i) {
const char character = address_str[i];
bool is_separator = (i + 1) % 3 == 0;
bool valid_address_character =
is_separator ? (character == ':') : base::IsHexDigit(character);
if (!valid_address_character)
return base::nullopt;
}
// Remove the separator and then parse the result.
std::string numbers_only;
base::RemoveChars(address_str, ":", &numbers_only);
std::vector<uint8_t> address_vector;
bool success = base::HexStringToBytes(numbers_only, &address_vector);
DCHECK(success);
std::array<uint8_t, 6> address_array;
std::copy_n(address_vector.begin(), 6, address_array.begin());
return address_array;
}
} // namespace
void BluetoothSystem::Create(mojom::BluetoothSystemRequest request,
mojom::BluetoothSystemClientPtr client) {
mojo::MakeStrongBinding(std::make_unique<BluetoothSystem>(std::move(client)),
......@@ -181,12 +223,63 @@ void BluetoothSystem::StopScan(StopScanCallback callback) {
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void BluetoothSystem::GetAvailableDevices(
GetAvailableDevicesCallback callback) {
switch (state_) {
case State::kUnsupported:
case State::kUnavailable:
case State::kPoweredOff:
case State::kTransitioning:
std::move(callback).Run({});
return;
case State::kPoweredOn:
break;
}
std::vector<dbus::ObjectPath> device_paths =
GetBluetoothDeviceClient()->GetDevicesForAdapter(active_adapter_.value());
std::vector<mojom::BluetoothDeviceInfoPtr> devices;
for (const auto& device_path : device_paths) {
auto* properties = GetBluetoothDeviceClient()->GetProperties(device_path);
base::Optional<std::array<uint8_t, 6>> parsed_address =
ParseAddress(properties->address.value());
if (!parsed_address) {
LOG(WARNING) << "Failed to parse device address '"
<< properties->address.value() << "' for "
<< device_path.value();
continue;
}
auto device_info = mojom::BluetoothDeviceInfo::New();
device_info->address = std::move(parsed_address.value());
device_info->name = properties->name.is_valid()
? base::make_optional(properties->name.value())
: base::nullopt;
device_info->connection_state =
properties->connected.value()
? mojom::BluetoothDeviceInfo::ConnectionState::kConnected
: mojom::BluetoothDeviceInfo::ConnectionState::kNotConnected;
device_info->is_paired = properties->paired.value();
// TODO(ortuno): Get the DeviceType from the device Class and Appearance.
devices.push_back(std::move(device_info));
}
std::move(callback).Run(std::move(devices));
}
bluez::BluetoothAdapterClient* BluetoothSystem::GetBluetoothAdapterClient() {
// Use AlternateBluetoothAdapterClient to avoid interfering with users of the
// regular BluetoothAdapterClient.
return bluez::BluezDBusManager::Get()->GetAlternateBluetoothAdapterClient();
}
bluez::BluetoothDeviceClient* BluetoothSystem::GetBluetoothDeviceClient() {
// Use AlternateBluetoothDeviceClient to avoid interfering with users of the
// regular BluetoothDeviceClient.
return bluez::BluezDBusManager::Get()->GetAlternateBluetoothDeviceClient();
}
void BluetoothSystem::UpdateStateAndNotifyIfNecessary() {
State old_state = state_;
if (active_adapter_) {
......
......@@ -16,6 +16,7 @@
namespace bluez {
class BluetoothAdapterClient;
class BluetoothDeviceClient;
}
namespace device {
......@@ -41,9 +42,11 @@ class BluetoothSystem : public mojom::BluetoothSystem,
void GetScanState(GetScanStateCallback callback) override;
void StartScan(StartScanCallback callback) override;
void StopScan(StopScanCallback callback) override;
void GetAvailableDevices(GetAvailableDevicesCallback callback) override;
private:
bluez::BluetoothAdapterClient* GetBluetoothAdapterClient();
bluez::BluetoothDeviceClient* GetBluetoothDeviceClient();
void UpdateStateAndNotifyIfNecessary();
......
......@@ -27,10 +27,22 @@
namespace device {
namespace {
// Adapter object paths
constexpr const char kDefaultAdapterObjectPathStr[] = "fake/hci0";
constexpr const char kAlternateAdapterObjectPathStr[] = "fake/hci1";
namespace {
// Device object paths
constexpr const char kDefaultDeviceObjectPathStr[] =
"fake/hci0/dev_00_11_22_AA_BB_CC";
// Device addresses
constexpr const char kDefaultDeviceAddressStr[] = "00:11:22:AA:BB:CC";
constexpr const char kAlternateDeviceAddressStr[] = "AA:BB:CC:DD:EE:FF";
constexpr const std::array<uint8_t, 6> kDefaultDeviceAddressArray = {
0x00, 0x11, 0x22, 0xAA, 0xBB, 0xCC};
bool GetValueAndReset(base::Optional<bool>* opt) {
base::Optional<bool> tmp;
......@@ -38,6 +50,20 @@ bool GetValueAndReset(base::Optional<bool>* opt) {
return tmp.value();
}
struct FakeDeviceOptions {
explicit FakeDeviceOptions(
const std::string& object_path = kDefaultDeviceObjectPathStr)
: object_path(object_path) {}
dbus::ObjectPath object_path;
std::string address{kDefaultDeviceAddressStr};
dbus::ObjectPath adapter_object_path{kDefaultAdapterObjectPathStr};
base::Optional<std::string> name;
bool paired = false;
bool connected = false;
};
// Exposes high-level methods to simulate Bluetooth events e.g. a new adapter
// was added, adapter power state changed, etc.
//
......@@ -459,6 +485,36 @@ class DEVICE_BLUETOOTH_EXPORT TestBluetoothDeviceClient
TestBluetoothDeviceClient() = default;
~TestBluetoothDeviceClient() override = default;
void SimulateDeviceAdded(const FakeDeviceOptions& options) {
ObjectPathToProperties::iterator it;
bool was_inserted;
std::tie(it, was_inserted) = device_object_paths_to_properties_.emplace(
options.object_path,
std::make_unique<Properties>(
base::BindLambdaForTesting([&](const std::string& property_name) {
for (auto& observer : observers_)
observer.DevicePropertyChanged(options.object_path,
property_name);
})));
DCHECK(was_inserted);
auto* properties = GetProperties(options.object_path);
properties->address.ReplaceValue(options.address);
if (options.name) {
properties->name.set_valid(true);
properties->name.ReplaceValue(options.name.value());
}
properties->paired.ReplaceValue(options.paired);
properties->connected.ReplaceValue(options.connected);
properties->adapter.ReplaceValue(options.adapter_object_path);
for (auto& observer : observers_)
observer.DeviceAdded(options.object_path);
}
// bluez::BluetoothDeviceClient
void Init(dbus::Bus* bus,
const std::string& bluetooth_service_name) override {}
......@@ -644,6 +700,16 @@ class BluetoothSystemTest : public DeviceServiceTestBase,
return result;
}
std::vector<mojom::BluetoothDeviceInfoPtr> GetAvailableDevicesAndWait(
const mojom::BluetoothSystemPtr& system) {
mojom::BluetoothSystemAsyncWaiter async_waiter(system.get());
std::vector<mojom::BluetoothDeviceInfoPtr> devices;
async_waiter.GetAvailableDevices(&devices);
return devices;
}
// mojom::BluetoothSystemClient
void OnStateChanged(mojom::BluetoothSystem::State state) override {
on_state_changed_states_.push_back(state);
......@@ -1595,4 +1661,102 @@ TEST_F(BluetoothSystemTest, Scan_PowerOffWhileScanning) {
on_scan_state_changed_states_);
}
// Tests addresses are parsed correctly.
TEST_F(BluetoothSystemTest, GetAvailableDevices_AddressParser) {
test_bluetooth_adapter_client_->SimulatePoweredOnAdapter();
auto system = CreateBluetoothSystem();
static const struct {
std::string object_path;
std::string address;
} device_cases[] = {
// Invalid addresses
{"1", "00:11"}, // Too short
{"2", "00:11:22:AA:BB:CC:DD"}, // Too long
{"3", "00-11-22-AA-BB-CC"}, // Invalid separator
{"4", "00:11:22:XX:BB:CC"}, // Invalid character
// Valid addresses
{"5", "00:11:22:aa:bb:cc"}, // Lowercase
{"6", "00:11:22:AA:BB:CC"}, // Uppercase
};
for (const auto& device : device_cases) {
FakeDeviceOptions fake_options(device.object_path);
fake_options.address = device.address;
test_bluetooth_device_client_->SimulateDeviceAdded(fake_options);
}
auto devices = GetAvailableDevicesAndWait(system);
ASSERT_EQ(2u, devices.size());
const std::array<uint8_t, 6> expected_address = {0x00, 0x11, 0x22,
0xAA, 0xBB, 0xCC};
EXPECT_EQ(expected_address, devices[0]->address);
EXPECT_EQ(expected_address, devices[1]->address);
}
// Tests all properties of devices are returned correctly.
TEST_F(BluetoothSystemTest, GetAvailableDevices) {
test_bluetooth_adapter_client_->SimulatePoweredOnAdapter();
auto system = CreateBluetoothSystem();
{
FakeDeviceOptions fake_options("1");
fake_options.address = kDefaultDeviceAddressStr;
fake_options.name = "Fake Device";
fake_options.paired = true;
fake_options.connected = true;
test_bluetooth_device_client_->SimulateDeviceAdded(fake_options);
}
{
FakeDeviceOptions fake_options("2");
fake_options.address = kAlternateDeviceAddressStr;
fake_options.name = base::nullopt;
fake_options.paired = false;
fake_options.connected = false;
test_bluetooth_device_client_->SimulateDeviceAdded(fake_options);
}
auto devices = GetAvailableDevicesAndWait(system);
ASSERT_EQ(2u, devices.size());
mojom::BluetoothDeviceInfoPtr device_with_name;
mojom::BluetoothDeviceInfoPtr device_without_name;
if (devices[0]->address == kDefaultDeviceAddressArray) {
device_with_name = std::move(devices[0]);
device_without_name = std::move(devices[1]);
} else {
device_with_name = std::move(devices[1]);
device_without_name = std::move(devices[0]);
}
EXPECT_EQ(device_with_name->name.value(), "Fake Device");
EXPECT_TRUE(device_with_name->is_paired);
EXPECT_EQ(device_with_name->connection_state,
mojom::BluetoothDeviceInfo::ConnectionState::kConnected);
EXPECT_FALSE(!!device_without_name->name);
EXPECT_FALSE(device_without_name->is_paired);
EXPECT_EQ(device_without_name->connection_state,
mojom::BluetoothDeviceInfo::ConnectionState::kNotConnected);
}
// Tests that if a device existed before BluetoothSystem was created, the
// device is still returned when calling GetAvailableDevices().
TEST_F(BluetoothSystemTest, GetAvailableDevices_ExistingDevice) {
test_bluetooth_adapter_client_->SimulatePoweredOnAdapter();
test_bluetooth_device_client_->SimulateDeviceAdded(FakeDeviceOptions());
auto system = CreateBluetoothSystem();
auto devices = GetAvailableDevicesAndWait(system);
ASSERT_EQ(1u, devices.size());
EXPECT_EQ(kDefaultDeviceAddressArray, devices[0]->address);
EXPECT_FALSE(devices[0]->name);
EXPECT_EQ(mojom::BluetoothDeviceInfo::ConnectionState::kNotConnected,
devices[0]->connection_state);
EXPECT_FALSE(devices[0]->is_paired);
}
} // namespace device
......@@ -170,6 +170,10 @@ interface BluetoothSystem {
// there is one in progress already.
// 2. Return more detailed error codes.
StopScan() => (StopScanResult result);
// Returns a list of devices we consider to be active. This includes
// recently seen devices, paired devices, and connected devices.
GetAvailableDevices() => (array<BluetoothDeviceInfo> devices);
};
// Interface used by clients of BluetoothSystem to get notified of events
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment