chromium/third_party/cardboard/src_overrides/sdk/sensors/android/device_gyroscope_sensor.cc

/*
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "third_party/cardboard/src/sdk/sensors/device_gyroscope_sensor.h"

#include <android/looper.h>
#include <android/sensor.h>
#include <stddef.h>

#include <memory>

#include "third_party/cardboard/src/sdk/sensors/accelerometer_data.h"
#include "third_party/cardboard/src/sdk/sensors/gyroscope_data.h"
#include "third_party/cardboard/src/sdk/util/constants.h"
#include "third_party/cardboard/src/sdk/util/logging.h"

// Workaround to avoid the inclusion of "android_native_app_glue.h.
#ifndef LOOPER_ID_USER
#define LOOPER_ID_USER 3
#endif

namespace cardboard {

namespace {

// Creates an Android sensor event queue for the current thread.
static ASensorEventQueue* CreateSensorQueue(ASensorManager* sensor_manager) {
  ALooper* event_looper = ALooper_forThread();

  if (event_looper == nullptr) {
    event_looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
    CARDBOARD_LOGI(
        "AccelerometerSensor: Created new event looper for gyroscope sensor "
        "capture thread.");
  }

  return ASensorManager_createEventQueue(sensor_manager, event_looper,
                                         LOOPER_ID_USER, nullptr, nullptr);
}

// Initialize Gyroscope sensor on Android. If available we try to
// request a SENSOR_TYPE_GYROSCOPE_UNCALIBRATED.
// Since both seem to be using the same underlying code this will work if the
// same integer is used as the mode as in java.
// The reason for using the uncalibrated gyroscope is that the regular
// gyro is calibrated with a bias offset in the system. As we cannot influence
// the behavior of this algorithm and it will affect the gyro while moving,
// it is safer to initialize to the uncalibrated one and handle the gyro bias
// estimation in Cardboard SDK.
enum PrivateSensors {
  // This is not defined in the native public sensors API, but it is in java.
  // If we define this here and it gets defined later in NDK this should
  // not compile.
  // It is defined in AOSP in hardware/libhardware/include/hardware/sensors.h
  ASENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14,
  ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED = 16,
  ASENSOR_TYPE_ADDITIONAL_INFO = 33,
};

static const ASensor* GetUncalibratedGyroscope(ASensorManager* sensor_manager) {
  return ASensorManager_getDefaultSensor(sensor_manager,
                                         ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED);
}

const ASensor* InitSensor(ASensorManager* sensor_manager) {
  const ASensor* gyro = GetUncalibratedGyroscope(sensor_manager);
  if (gyro != nullptr) {
    CARDBOARD_LOGI("Android Gyro Sensor: ASENSOR_TYPE_GYRO_UNCALIBRATED");
    return gyro;
  }
  CARDBOARD_LOGI("Android Gyro Sensor: ASENSOR_TYPE_GYROSCOPE");
  return ASensorManager_getDefaultSensor(sensor_manager,
                                         ASENSOR_TYPE_GYROSCOPE);
}

bool PollLooper(int timeout_ms, int* num_events) {
  void* source = nullptr;
  const int looper_id = ALooper_pollOnce(timeout_ms, NULL, num_events,
                                        reinterpret_cast<void**>(&source));
  if (looper_id != LOOPER_ID_USER) {
    return false;
  }
  if (*num_events <= 0) {
    return false;
  }
  return true;
}

class SensorEventQueueReader {
 public:
  SensorEventQueueReader(ASensorManager* manager, const ASensor* sensor)
      : manager_(manager),
        sensor_(sensor),
        queue_(CreateSensorQueue(manager_)) {}

  ~SensorEventQueueReader() {
    ASensorManager_destroyEventQueue(manager_, queue_);
  }

  bool Start() {
    ASensorEventQueue_enableSensor(queue_, sensor_);
    const int min_delay = ASensor_getMinDelay(sensor_);
    // Set sensor capture rate to the highest possible sampling rate.
    ASensorEventQueue_setEventRate(queue_, sensor_, min_delay);
    return true;
  }

  void Stop() { ASensorEventQueue_disableSensor(queue_, sensor_); }

  bool WaitForEvent(int timeout_ms, ASensorEvent* event) {
    int num_events;
    if (!PollLooper(timeout_ms, &num_events)) {
      return false;
    }
    return (ASensorEventQueue_getEvents(queue_, event, 1) > 0);
  }

  bool ReadEvent(ASensorEvent* event) {
    return (ASensorEventQueue_getEvents(queue_, event, 1) > 0);
  }

 private:
  ASensorManager* manager_;   // Owned by android library.
  const ASensor* sensor_;     // Owned by android library.
  ASensorEventQueue* queue_;  // Owned by this.
};

}  // namespace

// This struct holds android gyroscope specific sensor information.
struct DeviceGyroscopeSensor::SensorInfo {
  SensorInfo() : sensor_manager(nullptr), sensor(nullptr) {}
  ASensorManager* sensor_manager;
  const ASensor* sensor;
  std::unique_ptr<SensorEventQueueReader> reader;
};

namespace {

bool ParseGyroEvent(const ASensorEvent& event, GyroscopeData* sample) {
  if (event.type == ASENSOR_TYPE_ADDITIONAL_INFO) {
    CARDBOARD_LOGI("ParseGyroEvent discarding additional info sensor event");
    return false;
  }

  sample->sensor_timestamp_ns = event.timestamp;
  sample->system_timestamp = event.timestamp;  // Clock::time_point();
  // The event values in ASensorEvent (event, acceleration and
  // magnetic) are all in the same union type so they can be
  // accessed by event.
  if (event.type == ASENSOR_TYPE_GYROSCOPE) {
    sample->data = {event.vector.x, event.vector.y, event.vector.z};
    return true;
  } else if (event.type == ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
    // This is a special case when it is possible to initialize to
    // ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED
    sample->data = {event.vector.x, event.vector.y, event.vector.z};
    return true;
  } else {
    CARDBOARD_LOGE("ParseGyroEvent discarding unexpected sensor event type %d",
                   event.type);
  }

  return false;
}

}  // namespace

DeviceGyroscopeSensor::DeviceGyroscopeSensor()
    : sensor_info_(new SensorInfo()) {
#if __ANDROID_MIN_SDK_VERSION__ >= 26
  sensor_info_->sensor_manager =
      ASensorManager_getInstanceForPackage(Constants::kCardboardSdkPackageName);
#else
  // TODO: b/314792983 - Remove deprecated NDK methods.
  sensor_info_->sensor_manager = ASensorManager_getInstance();
#endif
  sensor_info_->sensor = InitSensor(sensor_info_->sensor_manager);
  if (!sensor_info_->sensor) {
    return;
  }

  sensor_info_->reader =
      std::unique_ptr<SensorEventQueueReader>(new SensorEventQueueReader(
          sensor_info_->sensor_manager, sensor_info_->sensor));
}

DeviceGyroscopeSensor::~DeviceGyroscopeSensor() {}

void DeviceGyroscopeSensor::PollForSensorData(
    int timeout_ms, std::vector<GyroscopeData>* results) const {
  results->clear();
  ASensorEvent event;
  if (!sensor_info_->reader->WaitForEvent(timeout_ms, &event)) {
    return;
  }
  do {
    GyroscopeData sample;
    if (ParseGyroEvent(event, &sample)) {
      results->push_back(sample);
    }
  } while (sensor_info_->reader->ReadEvent(&event));
}

bool DeviceGyroscopeSensor::Start() {
  if (!sensor_info_->reader) {
    CARDBOARD_LOGE("Could not start gyroscope sensor.");
    return false;
  }
  return sensor_info_->reader->Start();
}

void DeviceGyroscopeSensor::Stop() {
  if (!sensor_info_->reader) {
    return;
  }
  sensor_info_->reader->Stop();
}

}  // namespace cardboard