chromium/ash/sensor_info/sensor_provider_unittest.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/sensor_info/sensor_provider.h"

#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>

#include "ash/accelerometer/accelerometer_constants.h"
#include "ash/sensor_info/sensor_types.h"
#include "ash/test/ash_test_helper.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "chromeos/components/sensors/ash/sensor_hal_dispatcher.h"
#include "chromeos/components/sensors/fake_sensor_device.h"
#include "chromeos/components/sensors/fake_sensor_hal_server.h"
#include "chromeos/components/sensors/mojom/sensor.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

using ::chromeos::sensors::mojom::DeviceType;

namespace {

constexpr double kFakeScaleValue = 10.0;

constexpr int kFakeLidAccelerometerId = 1;
constexpr int kFakeBaseAccelerometerId = 2;
constexpr int kFakeBaseGyroscopeId = 3;
constexpr int kFakeLidAngleId = 4;

constexpr int64_t kFakeSampleData[] = {1, 2, 3};

class FakeObserver : public SensorObserver {
 public:
  void OnSensorUpdated(const SensorUpdate& update) override {
    for (int index = 0; index < static_cast<int>(SensorType::kSensorTypeCount);
         ++index) {
      auto source = static_cast<SensorType>(index);
      if (!update.has(source)) {
        continue;
      }

      if (source != SensorType::kLidAngle) {
        EXPECT_DOUBLE_EQ(update.get(source)->x,
                         kFakeSampleData[0] * kFakeScaleValue);
        EXPECT_DOUBLE_EQ(update.get(source)->y,
                         kFakeSampleData[1] * kFakeScaleValue);
        EXPECT_DOUBLE_EQ(update.get(source)->z,
                         kFakeSampleData[2] * kFakeScaleValue);
      } else {
        EXPECT_DOUBLE_EQ(update.get(source)->x, kFakeSampleData[0]);
      }
    }
    update_ = update;
  }

  SensorUpdate update_;
};

}  // namespace

class SensorProviderTest : public testing::Test {
 protected:
  void SetUp() override {
    chromeos::sensors::SensorHalDispatcher::Initialize();
    sensor_hal_server_ =
        std::make_unique<chromeos::sensors::FakeSensorHalServer>();
    provider_ = std::make_unique<SensorProvider>();
    provider_->AddObserver(&observer_);
  }

  void TearDown() override {
    chromeos::sensors::SensorHalDispatcher::Shutdown();
  }

  void AddDevice(int32_t iio_device_id,
                 std::set<DeviceType> types,
                 std::optional<std::string> scale,
                 std::optional<std::string> location) {
    std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data;
    int size = 0;
    if (base::Contains(types, DeviceType::ANGL)) {
      channels_data.resize(1);
      channels_data[0].id = "angl";
      channels_data[0].sample_data = 1;
      channels_data[0].attrs["raw"] = "1";
    }
    if (base::Contains(types, DeviceType::ACCEL)) {
      size += kNumberOfAxes;
      channels_data.resize(size);
      for (uint32_t i = 0; i < kNumberOfAxes; ++i) {
        channels_data[size - kNumberOfAxes + i].id = kAccelerometerChannels[i];
        channels_data[size - kNumberOfAxes + i].sample_data =
            kFakeSampleData[i];
      }
    }
    if (base::Contains(types, DeviceType::ANGLVEL)) {
      size += kNumberOfAxes;
      channels_data.resize(size);
      for (uint32_t i = 0; i < kNumberOfAxes; ++i) {
        channels_data[size - kNumberOfAxes + i].id = kGyroscopeChannels[i];
        channels_data[size - kNumberOfAxes + i].sample_data =
            kFakeSampleData[i];
      }
    }

    std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device(
        std::make_unique<chromeos::sensors::FakeSensorDevice>(
            std::move(channels_data)));
    if (scale.has_value()) {
      sensor_device->SetAttribute(chromeos::sensors::mojom::kScale,
                                  scale.value());
    }
    if (location.has_value()) {
      sensor_device->SetAttribute(chromeos::sensors::mojom::kLocation,
                                  location.value());
    }
    sensor_devices_[iio_device_id] = sensor_device.get();
    mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor;
    sensor_device->AddReceiver(sensor.BindNewPipeAndPassReceiver());
    sensor_hal_server_->GetSensorService()->SetDevice(
        iio_device_id, std::move(types), std::move(sensor_device));
  }

  void StartConnection() {
    chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
        sensor_hal_server_->PassRemote());
  }

  base::test::SingleThreadTaskEnvironment task_environment;

  FakeObserver observer_;
  std::unique_ptr<chromeos::sensors::FakeSensorHalServer> sensor_hal_server_;
  std::map<int32_t, chromeos::sensors::FakeSensorDevice*> sensor_devices_;
  std::unique_ptr<SensorProvider> provider_;
};

TEST_F(SensorProviderTest, CheckNoScale) {
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            std::nullopt, kLocationStrings[1]);
  StartConnection();
  provider_->EnableSensorReading();
  // Wait until all tasks done and no samples updated.
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{false, false, false, false, false};

  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_FALSE(observer_.update_.has(SensorType::kAccelerometerBase));
}

TEST_F(SensorProviderTest, CheckNoLocation) {
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), std::nullopt);

  StartConnection();
  provider_->EnableSensorReading();
  // Wait until all tasks done and no samples updated.
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{false, false, false, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_FALSE(observer_.update_.has(SensorType::kAccelerometerLid));
  EXPECT_FALSE(observer_.update_.has(SensorType::kAccelerometerBase));
}

TEST_F(SensorProviderTest, GetSamplesOfLidAccel) {
  AddDevice(kFakeLidAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[0]);
  StartConnection();
  provider_->EnableSensorReading();
  // Wait until a sample is received.
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{false, false, true, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_TRUE(sensor_hal_server_->GetSensorService()->HasReceivers());
  EXPECT_TRUE(base::Contains(sensor_devices_, kFakeLidAccelerometerId));
  EXPECT_TRUE(sensor_devices_[kFakeLidAccelerometerId]->HasReceivers());
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerLid));
}

TEST_F(SensorProviderTest, GetSamplesOfLidAngleAndLidAccel) {
  AddDevice(kFakeLidAngleId, std::set<DeviceType>{DeviceType::ANGL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[0]);
  // LidAngle sample update is generated by reading a sensor property, which
  // causes endless SensorsSamples updates if LidAngle is the only present
  // sensor. It is OK in production but not good for this test. So we add a
  // AccelerometerLid to let SensorProvider generate only one update.
  AddDevice(kFakeLidAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[0]);
  StartConnection();
  provider_->EnableSensorReading();
  // Wait until a sample is received.
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{true, false, true, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_TRUE(observer_.update_.has(SensorType::kLidAngle));
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerLid));
}

TEST_F(SensorProviderTest, GetSamplesOfBaseGyroscope) {
  AddDevice(kFakeBaseGyroscopeId, std::set<DeviceType>{DeviceType::ANGLVEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);

  StartConnection();
  provider_->EnableSensorReading();
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{false, false, false, true, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_TRUE(observer_.update_.has(SensorType::kGyroscopeBase));
}

TEST_F(SensorProviderTest, GetSamplesOfBaseGyroscopeAndBaseAccel) {
  // Creates device: GyroscopeBase and AccelerometerBase.
  AddDevice(kFakeBaseGyroscopeId, std::set<DeviceType>{DeviceType::ANGLVEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);

  StartConnection();
  provider_->EnableSensorReading();
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected1{false, true, false, true, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected1);
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerBase));
  EXPECT_TRUE(observer_.update_.has(SensorType::kGyroscopeBase));

  // Triggering SensorProvider::OnSensorServiceDisconnect.
  sensor_hal_server_->GetSensorService()->ClearReceivers();
  sensor_hal_server_->OnServerDisconnect();
  // Wait until the disconnect arrives at the dispatcher.
  base::RunLoop().RunUntilIdle();
  // Overwriting with invalid AccelerometerBase.
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            std::nullopt, std::nullopt);
  StartConnection();
  // Wait until the re-initialization done.
  base::RunLoop().RunUntilIdle();
  std::vector<bool> expected2{false, false, false, true, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected2);
  EXPECT_FALSE(observer_.update_.has(SensorType::kAccelerometerBase));
  EXPECT_TRUE(observer_.update_.has(SensorType::kGyroscopeBase));
}

TEST_F(SensorProviderTest, AddSensorsWhileSampling) {
  StartConnection();
  provider_->EnableSensorReading();
  // Wait until a sample is received.
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected1{false, false, false, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected1);

  // New device: AccelerometerBase.
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);

  // Wait until all setups are finished and no samples updated.
  base::RunLoop().RunUntilIdle();

  std::vector<bool> expected2{false, true, false, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected2);
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerBase));

  // New device: AccelerometerLid. Disconnect AccelerometerBase and reconnect.
  AddDevice(kFakeLidAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[0]);
  // Simulate a disconnection of the base accelerometer's mojo channel in IIO
  // Service with the reason of DEVICE_REMOVED.
  sensor_devices_[kFakeBaseAccelerometerId]->ClearReceiversWithReason(
      chromeos::sensors::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED,
      "Device was removed");
  // Overwrite the AccelerometerBase with valid device.
  AddDevice(kFakeBaseAccelerometerId, std::set<DeviceType>{DeviceType::ACCEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);

  // Wait until the disconnection and the re-initialization are done.
  base::RunLoop().RunUntilIdle();

  std::vector<bool> expected3{false, true, true, false, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected3);
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerLid));
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerBase));
}

TEST_F(SensorProviderTest, GetSamplesOfAccelGyroDevices) {
  // New device: GyroscopeBase and AccelerometerBase.
  AddDevice(kFakeBaseGyroscopeId,
            std::set<DeviceType>{DeviceType::ACCEL, DeviceType::ANGLVEL},
            base::NumberToString(kFakeScaleValue), kLocationStrings[1]);

  StartConnection();
  provider_->EnableSensorReading();
  base::RunLoop().RunUntilIdle();

  // This vector is the state vector for {LidAngle, AccelerometerBase,
  // AccelerometerLid, GyroscopeBase, GyroscopeLid} sensors, where true
  // meaning the sensor detected and false means the sensor not detected.
  std::vector<bool> expected{false, true, false, true, false};
  EXPECT_EQ(provider_->GetStateForTesting(), expected);
  EXPECT_TRUE(observer_.update_.has(SensorType::kAccelerometerBase));
  EXPECT_TRUE(observer_.update_.has(SensorType::kGyroscopeBase));
}

}  // namespace ash