chromium/services/device/generic_sensor/platform_sensor_reader_win.cc

// Copyright 2016 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/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "services/device/generic_sensor/platform_sensor_reader_win.h"

#include <objbase.h>

#include <Sensors.h>
#include <comdef.h>
#include <wrl/implements.h>

#include <iomanip>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/angle_conversions.h"
#include "base/numerics/math_constants.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/win/scoped_propvariant.h"
#include "services/device/generic_sensor/generic_sensor_consts.h"
#include "services/device/public/cpp/generic_sensor/platform_sensor_configuration.h"
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"

namespace device {

// Init params for the PlatformSensorReaderWin32.
struct ReaderInitParams {
  // ISensorDataReport::GetSensorValue is not const, therefore, report
  // cannot be passed as const ref.
  // ISensorDataReport* report - report that contains new sensor data.
  // SensorReading* reading    - out parameter that must be populated.
  // Returns HRESULT           - S_OK on success, otherwise error code.
  using ReaderFunctor = HRESULT (*)(ISensorDataReport* report,
                                    SensorReading* reading);
  SENSOR_TYPE_ID sensor_type_id;
  ReaderFunctor reader_func;
  base::TimeDelta min_reporting_interval;
};

namespace {

// Gets value from  the report for provided key.
bool GetReadingValueForProperty(REFPROPERTYKEY key,
                                ISensorDataReport* report,
                                double* value) {
  DCHECK(value);
  base::win::ScopedPropVariant variant_value;
  if (SUCCEEDED(report->GetSensorValue(key, variant_value.Receive()))) {
    if (variant_value.get().vt == VT_R8)
      *value = variant_value.get().dblVal;
    else if (variant_value.get().vt == VT_R4)
      *value = variant_value.get().fltVal;
    else
      return false;
    return true;
  }

  *value = 0;
  return false;
}

// Ambient light sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams> CreateAmbientLightReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_AMBIENT_LIGHT;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    double lux = 0.0;
    if (!GetReadingValueForProperty(SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX, report,
                                    &lux)) {
      return E_FAIL;
    }
    reading->als.value = lux;
    return S_OK;
  };
  return params;
}

// Accelerometer sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams> CreateAccelerometerReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_ACCELEROMETER_3D;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
    if (!GetReadingValueForProperty(SENSOR_DATA_TYPE_ACCELERATION_X_G, report,
                                    &x) ||
        !GetReadingValueForProperty(SENSOR_DATA_TYPE_ACCELERATION_Y_G, report,
                                    &y) ||
        !GetReadingValueForProperty(SENSOR_DATA_TYPE_ACCELERATION_Z_G, report,
                                    &z)) {
      return E_FAIL;
    }

    // Windows HW sensor integration requirements specify accelerometer
    // measurements conventions such as, the accelerometer sensor must expose
    // values that are proportional and in the same direction as the force of
    // gravity. Therefore, sensor hosted by the device at rest on a leveled
    // surface while the screen is facing towards the sky, must report -1G along
    // the Z axis.
    // https://msdn.microsoft.com/en-us/library/windows/hardware/dn642102(v=vs.85).aspx
    // Change sign of values, to report 'reaction force', and convert values
    // from G/s^2 to m/s^2 units.
    reading->accel.x = -x * base::kMeanGravityDouble;
    reading->accel.y = -y * base::kMeanGravityDouble;
    reading->accel.z = -z * base::kMeanGravityDouble;
    return S_OK;
  };
  return params;
}

// Gyroscope sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams> CreateGyroscopeReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_GYROMETER_3D;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
    if (!GetReadingValueForProperty(
            SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEGREES_PER_SECOND, report,
            &x) ||
        !GetReadingValueForProperty(
            SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGREES_PER_SECOND, report,
            &y) ||
        !GetReadingValueForProperty(
            SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGREES_PER_SECOND, report,
            &z)) {
      return E_FAIL;
    }

    // Values are converted from degrees to radians.
    reading->gyro.x = base::DegToRad(x);
    reading->gyro.y = base::DegToRad(y);
    reading->gyro.z = base::DegToRad(z);
    return S_OK;
  };
  return params;
}

// Magnetometer sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams> CreateMagnetometerReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_COMPASS_3D;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
    if (!GetReadingValueForProperty(
            SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_X_MILLIGAUSS, report,
            &x) ||
        !GetReadingValueForProperty(
            SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Y_MILLIGAUSS, report,
            &y) ||
        !GetReadingValueForProperty(
            SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH_Z_MILLIGAUSS, report,
            &z)) {
      return E_FAIL;
    }

    // Values are converted from Milligaus to Microtesla.
    reading->magn.x = x * kMicroteslaInMilligauss;
    reading->magn.y = y * kMicroteslaInMilligauss;
    reading->magn.z = z * kMicroteslaInMilligauss;
    return S_OK;
  };
  return params;
}

// AbsoluteOrientationEulerAngles sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams>
CreateAbsoluteOrientationEulerAnglesReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_INCLINOMETER_3D;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
    if (!GetReadingValueForProperty(SENSOR_DATA_TYPE_TILT_X_DEGREES, report,
                                    &x) ||
        !GetReadingValueForProperty(SENSOR_DATA_TYPE_TILT_Y_DEGREES, report,
                                    &y) ||
        !GetReadingValueForProperty(SENSOR_DATA_TYPE_TILT_Z_DEGREES, report,
                                    &z)) {
      return E_FAIL;
    }

    reading->orientation_euler.x = x;
    reading->orientation_euler.y = y;
    reading->orientation_euler.z = z;
    return S_OK;
  };
  return params;
}

// AbsoluteOrientationQuaternion sensor reader initialization parameters.
std::unique_ptr<ReaderInitParams>
CreateAbsoluteOrientationQuaternionReaderInitParams() {
  auto params = std::make_unique<ReaderInitParams>();
  params->sensor_type_id = SENSOR_TYPE_AGGREGATED_DEVICE_ORIENTATION;
  params->reader_func = [](ISensorDataReport* report, SensorReading* reading) {
    base::win::ScopedPropVariant quat_variant;
    HRESULT hr = report->GetSensorValue(SENSOR_DATA_TYPE_QUATERNION,
                                        quat_variant.Receive());
    if (FAILED(hr) || quat_variant.get().vt != (VT_VECTOR | VT_UI1) ||
        quat_variant.get().caub.cElems < 16) {
      return E_FAIL;
    }

    float* quat = reinterpret_cast<float*>(quat_variant.get().caub.pElems);

    reading->orientation_quat.x = quat[0];  // x*sin(Theta/2)
    reading->orientation_quat.y = quat[1];  // y*sin(Theta/2)
    reading->orientation_quat.z = quat[2];  // z*sin(Theta/2)
    reading->orientation_quat.w = quat[3];  // cos(Theta/2)
    return S_OK;
  };
  return params;
}

// Creates ReaderInitParams params structure. To implement support for new
// sensor types, new switch case should be added and appropriate fields must
// be set:
// sensor_type_id - GUID of the sensor supported by Windows.
// reader_func    - Functor that is responsible to populate SensorReading from
//                  ISensorDataReport data.
std::unique_ptr<ReaderInitParams> CreateReaderInitParamsForSensor(
    mojom::SensorType type) {
  switch (type) {
    case mojom::SensorType::AMBIENT_LIGHT:
      return CreateAmbientLightReaderInitParams();
    case mojom::SensorType::ACCELEROMETER:
      return CreateAccelerometerReaderInitParams();
    case mojom::SensorType::GYROSCOPE:
      return CreateGyroscopeReaderInitParams();
    case mojom::SensorType::MAGNETOMETER:
      return CreateMagnetometerReaderInitParams();
    case mojom::SensorType::ABSOLUTE_ORIENTATION_EULER_ANGLES:
      return CreateAbsoluteOrientationEulerAnglesReaderInitParams();
    case mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION:
      return CreateAbsoluteOrientationQuaternionReaderInitParams();
    default:
      NOTIMPLEMENTED();
      return nullptr;
  }
}

}  // namespace

// Class that implements ISensorEvents used by the ISensor interface to dispatch
// state and data change events.
class EventListener
    : public Microsoft::WRL::RuntimeClass<
          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
          ISensorEvents> {
 public:
  explicit EventListener(PlatformSensorReaderWin32* platform_sensor_reader)
      : platform_sensor_reader_(platform_sensor_reader) {
    DCHECK(platform_sensor_reader_);
  }

  EventListener(const EventListener&) = delete;
  EventListener& operator=(const EventListener&) = delete;

  static Microsoft::WRL::ComPtr<ISensorEvents> CreateInstance(
      PlatformSensorReaderWin32* platform_sensor_reader) {
    Microsoft::WRL::ComPtr<EventListener> event_listener =
        Microsoft::WRL::Make<EventListener>(platform_sensor_reader);
    Microsoft::WRL::ComPtr<ISensorEvents> sensor_events;
    HRESULT hr = event_listener.As(&sensor_events);
    DCHECK(SUCCEEDED(hr));
    return sensor_events;
  }

 protected:
  ~EventListener() override = default;

  // ISensorEvents interface
  IFACEMETHODIMP OnEvent(ISensor*, REFGUID, IPortableDeviceValues*) override {
    return S_OK;
  }

  IFACEMETHODIMP OnLeave(REFSENSOR_ID sensor_id) override {
    // If event listener is active and sensor is disconnected, notify client
    // about the error.
    platform_sensor_reader_->SensorError();
    platform_sensor_reader_->StopSensor();
    return S_OK;
  }

  IFACEMETHODIMP OnStateChanged(ISensor* sensor, SensorState state) override {
    if (sensor == nullptr)
      return E_INVALIDARG;

    if (state != SensorState::SENSOR_STATE_READY &&
        state != SensorState::SENSOR_STATE_INITIALIZING) {
      platform_sensor_reader_->SensorError();
      platform_sensor_reader_->StopSensor();
    }
    return S_OK;
  }

  IFACEMETHODIMP OnDataUpdated(ISensor* sensor,
                               ISensorDataReport* report) override {
    if (sensor == nullptr || report == nullptr)
      return E_INVALIDARG;

    // To get precise timestamp, we need to get delta between timestamp
    // provided in the report and current system time. Then the delta in
    // milliseconds is substracted from current high resolution timestamp.
    SYSTEMTIME report_time;
    HRESULT hr = report->GetTimestamp(&report_time);
    if (FAILED(hr))
      return hr;

    base::TimeTicks ticks_now = base::TimeTicks::Now();
    base::Time time_now = base::Time::NowFromSystemTime();

    const base::Time::Exploded exploded = {
        .year = report_time.wYear,
        .month = report_time.wMonth,
        .day_of_week = report_time.wDayOfWeek,
        .day_of_month = report_time.wDay,
        .hour = report_time.wHour,
        .minute = report_time.wMinute,
        .second = report_time.wSecond,
        .millisecond = report_time.wMilliseconds};

    base::Time timestamp;
    if (!base::Time::FromUTCExploded(exploded, &timestamp))
      return E_FAIL;

    base::TimeDelta delta = time_now - timestamp;

    SensorReading reading;
    reading.raw.timestamp =
        ((ticks_now - delta) - base::TimeTicks()).InSecondsF();

    // Discard update events that have non-monotonically increasing timestamp.
    if (last_sensor_reading_.raw.timestamp > reading.timestamp())
      return E_FAIL;

    hr = platform_sensor_reader_->SensorReadingChanged(report, &reading);
    if (SUCCEEDED(hr))
      last_sensor_reading_ = reading;
    return hr;
  }

 private:
  const raw_ptr<PlatformSensorReaderWin32> platform_sensor_reader_;
  SensorReading last_sensor_reading_;
};

// static
std::unique_ptr<PlatformSensorReaderWinBase> PlatformSensorReaderWin32::Create(
    mojom::SensorType type,
    Microsoft::WRL::ComPtr<ISensorManager> sensor_manager) {
  DCHECK(sensor_manager);

  auto params = CreateReaderInitParamsForSensor(type);
  if (!params)
    return nullptr;

  auto sensor = GetSensorForType(params->sensor_type_id, sensor_manager);
  if (!sensor)
    return nullptr;

  base::win::ScopedPropVariant min_interval;
  HRESULT hr = sensor->GetProperty(SENSOR_PROPERTY_MIN_REPORT_INTERVAL,
                                   min_interval.Receive());
  if (SUCCEEDED(hr) && min_interval.get().vt == VT_UI4) {
    params->min_reporting_interval =
        base::Milliseconds(min_interval.get().ulVal);
  }

  GUID interests[] = {SENSOR_EVENT_STATE_CHANGED, SENSOR_EVENT_DATA_UPDATED};
  hr = sensor->SetEventInterest(interests, std::size(interests));
  if (FAILED(hr))
    return nullptr;

  return base::WrapUnique(
      new PlatformSensorReaderWin32(sensor, std::move(params)));
}

// static
Microsoft::WRL::ComPtr<ISensor> PlatformSensorReaderWin32::GetSensorForType(
    REFSENSOR_TYPE_ID sensor_type,
    Microsoft::WRL::ComPtr<ISensorManager> sensor_manager) {
  Microsoft::WRL::ComPtr<ISensor> sensor;
  Microsoft::WRL::ComPtr<ISensorCollection> sensor_collection;
  HRESULT hr =
      sensor_manager->GetSensorsByType(sensor_type, &sensor_collection);
  if (FAILED(hr) || !sensor_collection)
    return sensor;

  ULONG count = 0;
  hr = sensor_collection->GetCount(&count);
  if (SUCCEEDED(hr) && count > 0)
    sensor_collection->GetAt(0, &sensor);
  return sensor;
}

PlatformSensorReaderWin32::PlatformSensorReaderWin32(
    Microsoft::WRL::ComPtr<ISensor> sensor,
    std::unique_ptr<ReaderInitParams> params)
    : init_params_(std::move(params)),
      com_sta_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      sensor_active_(false),
      client_(nullptr),
      sensor_(sensor),
      event_listener_(EventListener::CreateInstance(this)) {
  DCHECK(init_params_);
  DCHECK(init_params_->reader_func);
  DCHECK(sensor_);
}

void PlatformSensorReaderWin32::SetClient(Client* client) {
  base::AutoLock autolock(lock_);
  // Can be null.
  client_ = client;
}

void PlatformSensorReaderWin32::StopSensor() {
  base::AutoLock autolock(lock_);
  if (sensor_active_) {
    sensor_->SetEventSink(nullptr);
    sensor_active_ = false;
  }
}

PlatformSensorReaderWin32::~PlatformSensorReaderWin32() {
  DCHECK(com_sta_task_runner_->BelongsToCurrentThread());
}

bool PlatformSensorReaderWin32::StartSensor(
    const PlatformSensorConfiguration& configuration) {
  base::AutoLock autolock(lock_);

  if (!SetReportingInterval(configuration))
    return false;

  if (!sensor_active_) {
    com_sta_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&PlatformSensorReaderWin32::ListenSensorEvent,
                                  weak_factory_.GetWeakPtr()));
    sensor_active_ = true;
  }

  return true;
}

void PlatformSensorReaderWin32::ListenSensorEvent() {
  // Set event listener.
  HRESULT hr = sensor_->SetEventSink(event_listener_.Get());
  if (FAILED(hr)) {
    SensorError();
    StopSensor();
  }
}

bool PlatformSensorReaderWin32::SetReportingInterval(
    const PlatformSensorConfiguration& configuration) {
  Microsoft::WRL::ComPtr<IPortableDeviceValues> props;
  HRESULT hr = ::CoCreateInstance(CLSID_PortableDeviceValues, nullptr,
                                  CLSCTX_ALL, IID_PPV_ARGS(&props));
  if (FAILED(hr)) {
    static bool logged_failure = false;
    if (!logged_failure) {
      LOG(ERROR) << "Unable to create instance of PortableDeviceValues: "
                 << _com_error(hr).ErrorMessage() << " (0x" << std::hex
                 << std::uppercase << std::setfill('0') << std::setw(8) << hr
                 << ")";
      logged_failure = true;
    }
    return false;
  }

  unsigned interval =
      (1 / configuration.frequency()) * base::Time::kMillisecondsPerSecond;
  hr = props->SetUnsignedIntegerValue(SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL,
                                      interval);
  if (FAILED(hr))
    return false;

  Microsoft::WRL::ComPtr<IPortableDeviceValues> return_props;
  hr = sensor_->SetProperties(props.Get(), &return_props);
  return SUCCEEDED(hr);
}

HRESULT PlatformSensorReaderWin32::SensorReadingChanged(
    ISensorDataReport* report,
    SensorReading* reading) {
  base::AutoLock autolock(lock_);
  if (!client_)
    return E_FAIL;

  HRESULT hr = init_params_->reader_func(report, reading);
  if (SUCCEEDED(hr))
    client_->OnReadingUpdated(*reading);
  return hr;
}

void PlatformSensorReaderWin32::SensorError() {
  base::AutoLock autolock(lock_);
  if (client_)
    client_->OnSensorError();
}

base::TimeDelta PlatformSensorReaderWin32::GetMinimalReportingInterval() const {
  return init_params_->min_reporting_interval;
}

}  // namespace device