chromium/ash/sensor_info/sensor_provider.cc

// 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