blob: 71cb1847c125cb5485c0d541e0f9017c254cd119 [file] [log] [blame]
// Copyright 2018 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 "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)),
std::move(request));
}
BluetoothSystem::BluetoothSystem(mojom::BluetoothSystemClientPtr client) {
client_ptr_ = std::move(client);
GetBluetoothAdapterClient()->AddObserver(this);
std::vector<dbus::ObjectPath> object_paths =
GetBluetoothAdapterClient()->GetAdapters();
if (object_paths.empty())
return;
active_adapter_ = object_paths[0];
auto* properties =
GetBluetoothAdapterClient()->GetProperties(active_adapter_.value());
state_ = properties->powered.value() ? State::kPoweredOn : State::kPoweredOff;
}
BluetoothSystem::~BluetoothSystem() = default;
void BluetoothSystem::AdapterAdded(const dbus::ObjectPath& object_path) {
if (active_adapter_)
return;
active_adapter_ = object_path;
UpdateStateAndNotifyIfNecessary();
}
void BluetoothSystem::AdapterRemoved(const dbus::ObjectPath& object_path) {
DCHECK(active_adapter_);
if (active_adapter_.value() != object_path)
return;
active_adapter_ = base::nullopt;
std::vector<dbus::ObjectPath> object_paths =
GetBluetoothAdapterClient()->GetAdapters();
for (const auto& new_object_path : object_paths) {
// The removed adapter is still included in GetAdapters().
if (new_object_path == object_path)
continue;
active_adapter_ = new_object_path;
break;
}
UpdateStateAndNotifyIfNecessary();
}
void BluetoothSystem::AdapterPropertyChanged(
const dbus::ObjectPath& object_path,
const std::string& property_name) {
// AdapterPropertyChanged is called for each property in the adapter before
// AdapterAdded is called. Immediately return in this case.
if (!active_adapter_)
return;
if (active_adapter_.value() != object_path)
return;
auto* properties =
GetBluetoothAdapterClient()->GetProperties(active_adapter_.value());
if (properties->powered.name() == property_name)
UpdateStateAndNotifyIfNecessary();
else if (properties->discovering.name() == property_name)
client_ptr_->OnScanStateChanged(GetScanStateFromActiveAdapter());
}
void BluetoothSystem::GetState(GetStateCallback callback) {
std::move(callback).Run(state_);
}
void BluetoothSystem::SetPowered(bool powered, SetPoweredCallback callback) {
switch (state_) {
case State::kUnsupported:
case State::kUnavailable:
std::move(callback).Run(SetPoweredResult::kFailedBluetoothUnavailable);
return;
case State::kTransitioning:
std::move(callback).Run(SetPoweredResult::kFailedInProgress);
return;
case State::kPoweredOff:
case State::kPoweredOn:
break;
}
if ((state_ == State::kPoweredOn) == powered) {
std::move(callback).Run(SetPoweredResult::kSuccess);
return;
}
DCHECK_NE(state_, State::kTransitioning);
state_ = State::kTransitioning;
client_ptr_->OnStateChanged(state_);
GetBluetoothAdapterClient()
->GetProperties(active_adapter_.value())
->powered.Set(powered,
base::BindRepeating(&BluetoothSystem::OnSetPoweredFinished,
weak_ptr_factory_.GetWeakPtr(),
base::Passed(&callback)));
}
void BluetoothSystem::GetScanState(GetScanStateCallback callback) {
switch (state_) {
case State::kUnsupported:
case State::kUnavailable:
std::move(callback).Run(ScanState::kNotScanning);
return;
case State::kPoweredOff:
// The Scan State when the adapter is Off should always be
// kNotScanning, but the underlying layer makes no guarantees of this.
// To avoid hiding a bug in the underlying layer, get the state from
// the adapter even if it's Off.
case State::kTransitioning:
case State::kPoweredOn:
break;
}
std::move(callback).Run(GetScanStateFromActiveAdapter());
}
void BluetoothSystem::StartScan(StartScanCallback callback) {
switch (state_) {
case State::kUnsupported:
case State::kUnavailable:
case State::kPoweredOff:
case State::kTransitioning:
std::move(callback).Run(StartScanResult::kFailedBluetoothUnavailable);
return;
case State::kPoweredOn:
break;
}
GetBluetoothAdapterClient()->StartDiscovery(
active_adapter_.value(),
base::BindOnce(&BluetoothSystem::OnStartDiscovery,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void BluetoothSystem::StopScan(StopScanCallback callback) {
switch (state_) {
case State::kUnsupported:
case State::kUnavailable:
case State::kPoweredOff:
case State::kTransitioning:
std::move(callback).Run(StopScanResult::kFailedBluetoothUnavailable);
return;
case State::kPoweredOn:
break;
}
GetBluetoothAdapterClient()->StopDiscovery(
active_adapter_.value(),
base::BindOnce(&BluetoothSystem::OnStopDiscovery,
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_) {
auto* properties =
GetBluetoothAdapterClient()->GetProperties(active_adapter_.value());
state_ =
properties->powered.value() ? State::kPoweredOn : State::kPoweredOff;
} else {
state_ = State::kUnavailable;
}
if (old_state != state_)
client_ptr_->OnStateChanged(state_);
}
BluetoothSystem::ScanState BluetoothSystem::GetScanStateFromActiveAdapter() {
bool discovering = GetBluetoothAdapterClient()
->GetProperties(active_adapter_.value())
->discovering.value();
return discovering ? ScanState::kScanning : ScanState::kNotScanning;
}
void BluetoothSystem::OnSetPoweredFinished(SetPoweredCallback callback,
bool succeeded) {
if (!succeeded) {
// We change |state_| to `kTransitioning` before trying to set 'powered'. If
// the call to set 'powered' fails, then we need to change it back to
// `kPoweredOn` or `kPoweredOff` depending on the `active_adapter_` state.
UpdateStateAndNotifyIfNecessary();
}
std::move(callback).Run(succeeded ? SetPoweredResult::kSuccess
: SetPoweredResult::kFailedUnknownReason);
}
void BluetoothSystem::OnStartDiscovery(
StartScanCallback callback,
const base::Optional<bluez::BluetoothAdapterClient::Error>& error) {
// TODO(https://crbug.com/897996): Use the name and message in |error| to
// return more specific error codes.
std::move(callback).Run(error ? StartScanResult::kFailedUnknownReason
: StartScanResult::kSuccess);
}
void BluetoothSystem::OnStopDiscovery(
StopScanCallback callback,
const base::Optional<bluez::BluetoothAdapterClient::Error>& error) {
// TODO(https://crbug.com/897996): Use the name and message in |error| to
// return more specific error codes.
std::move(callback).Run(error ? StopScanResult::kFailedUnknownReason
: StopScanResult::kSuccess);
}
} // namespace device