// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/sensor_info/sensor_provider.h"
#include <cstddef>
#include <iterator>
#include <utility>
#include "ash/sensor_info/sensor_types.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chromeos/components/sensors/ash/sensor_hal_dispatcher.h"
#include "chromeos/components/sensors/mojom/sensor.mojom.h"
namespace ash {
using ::chromeos::sensors::mojom::DeviceType;
namespace {
// Delay of the reconnection to Sensor Hal Dispatcher.
constexpr base::TimeDelta kDelayReconnect = base::Seconds(1);
// Timeout for getting lid_angle samples.
constexpr base::TimeDelta kLidAngleTimeout = base::Seconds(1);
constexpr double kReadFrequencyInHz = 100.0;
} // namespace
SensorProvider::DeviceState::DeviceState() {
for (int index = 0; index < static_cast<int>(SensorType::kSensorTypeCount);
++index) {
present_.push_back(false);
}
}
SensorProvider::DeviceState::~DeviceState() = default;
bool SensorProvider::DeviceState::AllSensorsFound() const {
for (int index = 0; index < static_cast<int>(SensorType::kSensorTypeCount);
++index) {
if (!present_[index]) {
return false;
}
}
return true;
}
void SensorProvider::DeviceState::Reset() {
for (int index = 0; index < static_cast<int>(SensorType::kSensorTypeCount);
++index) {
present_[index] = false;
}
}
bool SensorProvider::DeviceState::CompareUpdate(SensorUpdate update) const {
for (int index = 0; index < static_cast<int>(SensorType::kSensorTypeCount);
++index) {
auto source = static_cast<SensorType>(index);
if (!present_[index]) {
if (update.has(source)) {
LOG(ERROR) << "SensorUpdate has extra source: "
<< static_cast<int>(source);
}
continue;
}
if (update.has(source)) {
continue;
}
return false;
}
return true;
}
void SensorProvider::DeviceState::AddSource(SensorType source) {
present_[static_cast<int>(source)] = true;
}
void SensorProvider::DeviceState::RemoveSource(SensorType source) {
present_[static_cast<int>(source)] = false;
}
bool SensorProvider::DeviceState::GetSource(SensorType source) const {
return present_[static_cast<int>(source)];
}
std::vector<bool> SensorProvider::DeviceState::GetStatesForTesting() const {
return present_;
}
SensorProvider::SensorData::SensorData() = default;
SensorProvider::SensorData::~SensorData() = default;
SensorProvider::SensorProvider() {
RegisterSensorClient();
}
SensorProvider::~SensorProvider() = default;
void SensorProvider::SetUpChannel(
mojo::PendingRemote<chromeos::sensors::mojom::SensorService>
pending_remote) {
if (sensor_service_remote_.is_bound()) {
LOG(ERROR) << "Ignoring the second Remote<SensorService>";
return;
}
sensor_service_remote_.Bind(std::move(pending_remote));
sensor_service_remote_.set_disconnect_handler(
base::BindOnce(&SensorProvider::OnSensorServiceDisconnect,
weak_ptr_factory_.GetWeakPtr()));
SetNewDevicesObserver();
QueryDevices();
}
void SensorProvider::OnSensorServiceDisconnect() {
LOG(ERROR) << "OnSensorServiceDisconnect";
ResetSensorService();
}
std::vector<bool> SensorProvider::GetStateForTesting() {
return current_state_.GetStatesForTesting(); // IN-TEST
}
void SensorProvider::OnNewDeviceAdded(int32_t iio_device_id,
const std::vector<DeviceType>& types) {
for (const auto& type : types) {
if (type == DeviceType::ACCEL || type == DeviceType::ANGL ||
type == DeviceType::ANGLVEL) {
RegisterSensor(type, iio_device_id);
}
}
}
void SensorProvider::RegisterSensorClient() {
auto* dispatcher = chromeos::sensors::SensorHalDispatcher::GetInstance();
if (!dispatcher) {
// In unit tests, SensorHalDispatcher is not initialized.
return;
}
dispatcher->RegisterClient(sensor_hal_client_.BindNewPipeAndPassRemote());
sensor_hal_client_.set_disconnect_handler(
base::BindOnce(&SensorProvider::OnSensorHalClientFailure,
weak_ptr_factory_.GetWeakPtr()));
}
void SensorProvider::OnSensorHalClientFailure() {
LOG(ERROR) << "OnSensorHalClientFailure";
ResetSensorService();
sensor_hal_client_.reset();
// It's expected that SensorHalDispatcher will restart after failure,
// so it's OK to retry forever here.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SensorProvider::RegisterSensorClient,
weak_ptr_factory_.GetWeakPtr()),
kDelayReconnect);
}
void SensorProvider::SetNewDevicesObserver() {
DCHECK(sensor_service_remote_.is_bound());
DCHECK(!new_devices_observer_.is_bound());
if (current_state_.AllSensorsFound()) {
// Don't need any further devices.
return;
}
sensor_service_remote_->RegisterNewDevicesObserver(
new_devices_observer_.BindNewPipeAndPassRemote());
new_devices_observer_.set_disconnect_handler(
base::BindOnce(&SensorProvider::OnNewDevicesObserverDisconnect,
weak_ptr_factory_.GetWeakPtr()));
}
void SensorProvider::OnNewDevicesObserverDisconnect() {
LOG(ERROR)
<< "OnNewDevicesObserverDisconnect, resetting SensorService as IIO "
"Service should be destructed and waiting for the relaunch of it.";
ResetSensorService();
}
void SensorProvider::ResetSensorService() {
new_devices_observer_.reset();
sensor_service_remote_.reset();
// Discard SensorData to prevent sensors removed while disconnected.
ResetStates();
}
void SensorProvider::QueryDevices() {
DCHECK(sensor_service_remote_.is_bound());
// Get lid_angle sensors
sensor_service_remote_->GetDeviceIds(
DeviceType::ANGL, base::BindOnce(&SensorProvider::OnLidAngleIds,
weak_ptr_factory_.GetWeakPtr()));
// Get accelerometer sensors
sensor_service_remote_->GetDeviceIds(
DeviceType::ACCEL, base::BindOnce(&SensorProvider::OnAccelerometerIds,
weak_ptr_factory_.GetWeakPtr()));
// Get gyroscope sensors
sensor_service_remote_->GetDeviceIds(
DeviceType::ANGLVEL, base::BindOnce(&SensorProvider::OnGyroscopeIds,
weak_ptr_factory_.GetWeakPtr()));
}
void SensorProvider::OnLidAngleIds(const std::vector<int32_t>& lid_angle_ids) {
for (int32_t id : lid_angle_ids) {
RegisterSensor(DeviceType::ANGL, id);
}
}
void SensorProvider::OnAccelerometerIds(
const std::vector<int32_t>& accelerometer_ids) {
for (int32_t id : accelerometer_ids) {
RegisterSensor(DeviceType::ACCEL, id);
}
}
void SensorProvider::OnGyroscopeIds(const std::vector<int32_t>& gyroscope_ids) {
for (int32_t id : gyroscope_ids) {
RegisterSensor(DeviceType::ANGLVEL, id);
}
}
void SensorProvider::RegisterSensor(DeviceType device_type, int32_t id) {
auto& sensor = sensors_[id][device_type];
if (sensor.ignored) {
// Something went wrong in the previous initialization. Ignoring this
// sensor.
return;
}
if (sensor.remote.is_bound()) {
// Has already been registered.
return;
}
DCHECK(!sensor.samples_observer.get())
<< "Registering a sensor for device id " << id << " type " << device_type
<< "when there already exists one.";
if (!sensor_service_remote_.is_bound()) {
// Something went wrong. Skipping here.
LOG(ERROR) << "SensorService is not initialized.";
return;
}
sensor_service_remote_->GetDevice(id,
sensor.remote.BindNewPipeAndPassReceiver());
sensor.remote.set_disconnect_with_reason_handler(
base::BindOnce(&SensorProvider::OnSensorRemoteDisconnect,
weak_ptr_factory_.GetWeakPtr(), device_type, id));
if (device_type == DeviceType::ANGL) {
// Samples from ANGL range from 0-180 (which means lid_angle degree), so set
// scale to 1.
// Location of lid_angle device is meaningless, so set to kOther.
sensor.location = SensorLocation::kOther;
sensor.scale = 1.0;
if (base::Contains(type_to_sensor_id_, SensorType::kLidAngle)) {
LOG(WARNING) << "Duplicated location source "
<< "id_angle"
<< " of sensor id: " << id << ", and sensor id: "
<< type_to_sensor_id_[SensorType::kLidAngle];
IgnoreSensor(device_type, id);
return;
}
current_state_.AddSource(SensorType::kLidAngle);
type_to_sensor_id_[SensorType::kLidAngle] = id;
}
std::vector<std::string> attr_names;
if (!sensor.location.has_value()) {
attr_names.push_back(chromeos::sensors::mojom::kLocation);
}
if (!sensor.scale.has_value()) {
attr_names.push_back(chromeos::sensors::mojom::kScale);
}
if (attr_names.empty() || device_type == DeviceType::ANGL) {
// Create the observer directly if the attributes have already been
// retrieved. Won't create SamplesObserver for lid_angle sensor.
CreateSensorSamplesObserver(device_type, id);
return;
}
sensor.remote->GetAttributes(
attr_names,
base::BindOnce(&SensorProvider::OnAttributes,
weak_ptr_factory_.GetWeakPtr(), device_type, id));
}
void SensorProvider::OnSensorRemoteDisconnect(DeviceType device_type,
int32_t id,
uint32_t custom_reason_code,
const std::string& description) {
auto& sensor = sensors_[id][device_type];
auto reason =
static_cast<chromeos::sensors::mojom::SensorDeviceDisconnectReason>(
custom_reason_code);
LOG(WARNING) << "OnSensorRemoteDisconnect: " << id << ", reason: " << reason
<< ", description: " << description;
switch (reason) {
case chromeos::sensors::mojom::SensorDeviceDisconnectReason::
IIOSERVICE_CRASHED:
ResetSensorService();
break;
case chromeos::sensors::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED:
// This sensor is not in use.
if (sensor.ignored || !sensor.location.has_value() ||
!sensor.scale.has_value()) {
sensors_[id].erase(device_type);
} else {
// Reset usages & states, and restart the mojo devices initialization.
ResetStates();
}
break;
}
}
void SensorProvider::ResetStates() {
current_state_.Reset();
sensors_.clear();
type_to_sensor_id_.clear();
update_.Reset();
if (sensor_service_remote_.is_bound()) {
QueryDevices();
}
}
void SensorProvider::IgnoreSensor(DeviceType device_type, int32_t id) {
auto& sensor = sensors_[id][device_type];
LOG(WARNING) << "Ignoring sensor with id: " << id
<< " device_type:" << device_type;
sensor.ignored = true;
sensor.remote.reset();
sensor.samples_observer.reset();
}
void SensorProvider::EnableSensorReading() {
sensor_read_on_ = true;
EnableSensorReadingInternal();
}
void SensorProvider::EnableSensorReadingInternal() {
// Starts Sensor Reading if all sensors are ready.
if (sensor_read_on_ && CheckSensorSamplesObserver()) {
GetLidAngleUpdate();
for (auto& sensor : sensors_) {
for (auto& type : sensor.second) {
if (!type.second.samples_observer.get()) {
continue;
}
type.second.samples_observer->SetEnabled(true);
}
}
}
}
void SensorProvider::StopSensorReading() {
for (auto& sensor : sensors_) {
for (auto& type : sensor.second) {
if (!type.second.samples_observer.get()) {
continue;
}
type.second.samples_observer->SetEnabled(false);
}
}
sensor_read_on_ = false;
}
void SensorProvider::CreateSensorSamplesObserver(DeviceType device_type,
int32_t id) {
auto& sensor = sensors_[id][device_type];
DCHECK(sensor.remote.is_bound());
DCHECK(!sensor.ignored);
DCHECK(sensor.scale.has_value() && sensor.location.has_value());
if (device_type == DeviceType::ACCEL || device_type == DeviceType::ANGLVEL) {
sensor.samples_observer = std::make_unique<AccelGyroSamplesObserver>(
id, std::move(sensor.remote), sensor.scale.value(),
base::BindRepeating(&SensorProvider::OnSampleUpdatedCallback,
weak_ptr_factory_.GetWeakPtr(), device_type),
device_type, kReadFrequencyInHz);
}
EnableSensorReadingInternal();
}
bool SensorProvider::CheckSensorSamplesObserver() {
for (auto& sensor_id : sensors_) {
for (auto& [type, sensor_data] : sensor_id.second) {
if (type == DeviceType::ANGL || sensor_data.ignored) {
continue;
}
if (!sensor_data.samples_observer.get()) {
return false;
}
}
}
return true;
}
void SensorProvider::OnAttributes(
DeviceType device_type,
int32_t id,
const std::vector<std::optional<std::string>>& values) {
auto& sensor = sensors_[id][device_type];
DCHECK(sensor.remote.is_bound());
auto val_it = values.begin();
SensorType source;
if (!sensor.location.has_value()) {
if (val_it == values.end()) {
LOG(ERROR) << "values doesn't contain location attribute.";
IgnoreSensor(device_type, id);
return;
}
if (!val_it->has_value()) {
LOG(WARNING) << "No location attribute for sensor with id: " << id;
IgnoreSensor(device_type, id);
return;
}
auto* it = base::ranges::find(kLocationStrings, val_it->value());
if (it == std::end(kLocationStrings)) {
LOG(WARNING) << "Unrecognized location: " << val_it->value()
<< " for device with id: " << id;
IgnoreSensor(device_type, id);
return;
}
SensorLocation location = static_cast<SensorLocation>(
std::distance(std::begin(kLocationStrings), it));
sensor.location = location;
if (device_type == DeviceType::ACCEL) {
if (location == SensorLocation::kBase) {
source = SensorType::kAccelerometerBase;
} else {
source = SensorType::kAccelerometerLid;
}
} else {
if (location == SensorLocation::kBase) {
source = SensorType::kGyroscopeBase;
} else {
source = SensorType::kGyroscopeLid;
}
}
if (base::Contains(type_to_sensor_id_, source)) {
LOG(WARNING) << "Duplicated location source " << static_cast<int>(source)
<< " of sensor id: " << id
<< ", and sensor id: " << type_to_sensor_id_[source];
IgnoreSensor(device_type, id);
return;
}
val_it++;
}
if (!sensor.scale.has_value()) {
if (val_it == values.end()) {
LOG(ERROR) << "values doesn't contain scale attribute.";
IgnoreSensor(device_type, id);
return;
}
double scale = 0.0;
if (!val_it->has_value() ||
!base::StringToDouble(val_it->value(), &scale)) {
LOG(ERROR) << "Invalid scale: " << val_it->value_or("")
<< ", for accel with id: " << id;
IgnoreSensor(device_type, id);
return;
}
sensor.scale = scale;
}
DCHECK(!sensor.ignored);
current_state_.AddSource(source);
type_to_sensor_id_[source] = id;
CreateSensorSamplesObserver(device_type, id);
}
void SensorProvider::GetLidAngleUpdate() {
if (current_state_.GetSource(SensorType::kLidAngle) &&
!update_.has(SensorType::kLidAngle)) {
sensors_[type_to_sensor_id_[SensorType::kLidAngle]][DeviceType::ANGL]
.remote->GetChannelsAttributes(
{0}, {"raw"},
base::BindRepeating(&SensorProvider::OnLidAngleValue,
weak_ptr_factory_.GetWeakPtr()));
}
}
void SensorProvider::OnSampleUpdatedCallback(DeviceType device_type,
int iio_device_id,
std::vector<float> sample) {
DCHECK_EQ(sample.size(), kNumberOfAxes);
SensorType key;
bool find = false;
for (auto& i : type_to_sensor_id_) {
if (i.second == iio_device_id) {
key = i.first;
if ((key == SensorType::kAccelerometerBase ||
key == SensorType::kAccelerometerLid) &&
device_type == DeviceType::ACCEL) {
find = true;
break;
} else if ((key == SensorType::kGyroscopeBase ||
key == SensorType::kGyroscopeLid) &&
device_type == DeviceType::ANGLVEL) {
find = true;
break;
}
}
}
if (!find) {
LOG(ERROR) << "Couldn't find the the SensorType that matches DeviceType: "
<< device_type << " and device_id: " << iio_device_id;
return;
}
update_.Set(key, sample[0], sample[1], sample[2]);
if (!current_state_.CompareUpdate(update_)) {
// Wait for other sensors to be updated.
return;
}
NotifySensorUpdated(update_);
}
void SensorProvider::OnLidAngleValue(
const std::vector<std::optional<std::string>>& values) {
if (values[0].has_value()) {
int angle;
if (base::StringToInt(values[0].value(), &angle)) {
update_.Set(SensorType::kLidAngle, angle);
if (current_state_.CompareUpdate(update_)) {
NotifySensorUpdated(update_);
}
return;
}
LOG(ERROR) << "Failed to convert lid angle sample into integer.";
} else {
LOG(ERROR) << "Lid Angle device couldn't get angle return.";
}
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SensorProvider::GetLidAngleUpdate,
weak_ptr_factory_.GetWeakPtr()),
kLidAngleTimeout);
}
void SensorProvider::NotifySensorUpdated(SensorUpdate update) {
for (auto& observer : observers_) {
observer.OnSensorUpdated(update);
}
update_.Reset();
GetLidAngleUpdate();
}
void SensorProvider::AddObserver(SensorObserver* observer) {
observers_.AddObserver(observer);
}
void SensorProvider::RemoveObserver(SensorObserver* observer) {
observers_.RemoveObserver(observer);
}
} // namespace ash