// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/device_sensors/sensor_manager_android.h"

#include <string.h>

#include "base/android/jni_android.h"
#include "base/bind.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "content/browser/device_sensors/inertial_sensor_consts.h"
#include "content/public/browser/browser_thread.h"
#include "jni/DeviceSensors_jni.h"

using base::android::AttachCurrentThread;

namespace {

enum OrientationSensorType {
  NOT_AVAILABLE = 0,
  ROTATION_VECTOR = 1,
  ACCELEROMETER_MAGNETIC = 2,
  ORIENTATION_SENSOR_MAX = 3,
};

void UpdateDeviceOrientationHistogram(OrientationSensorType type) {
  UMA_HISTOGRAM_ENUMERATION("InertialSensor.DeviceOrientationSensorAndroid",
                            type,
                            ORIENTATION_SENSOR_MAX);
}

}  // namespace

namespace content {

SensorManagerAndroid::SensorManagerAndroid()
    : number_active_device_motion_sensors_(0),
      device_light_buffer_(nullptr),
      device_motion_buffer_(nullptr),
      device_orientation_buffer_(nullptr),
      is_light_buffer_ready_(false),
      is_motion_buffer_ready_(false),
      is_orientation_buffer_ready_(false),
      is_using_backup_sensors_for_orientation_(false),
      is_shutdown_(false) {
  memset(received_motion_data_, 0, sizeof(received_motion_data_));
  device_sensors_.Reset(Java_DeviceSensors_getInstance(
      AttachCurrentThread(), base::android::GetApplicationContext()));
}

SensorManagerAndroid::~SensorManagerAndroid() {
}

bool SensorManagerAndroid::Register(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

SensorManagerAndroid* SensorManagerAndroid::GetInstance() {
  return Singleton<SensorManagerAndroid,
                   LeakySingletonTraits<SensorManagerAndroid> >::get();
}

void SensorManagerAndroid::GotOrientation(
    JNIEnv*, jobject, double alpha, double beta, double gamma) {
  base::AutoLock autolock(orientation_buffer_lock_);

  if (!device_orientation_buffer_)
    return;

  device_orientation_buffer_->seqlock.WriteBegin();
  device_orientation_buffer_->data.alpha = alpha;
  device_orientation_buffer_->data.hasAlpha = true;
  device_orientation_buffer_->data.beta = beta;
  device_orientation_buffer_->data.hasBeta = true;
  device_orientation_buffer_->data.gamma = gamma;
  device_orientation_buffer_->data.hasGamma = true;
  device_orientation_buffer_->seqlock.WriteEnd();

  if (!is_orientation_buffer_ready_) {
    SetOrientationBufferReadyStatus(true);
    UpdateDeviceOrientationHistogram(is_using_backup_sensors_for_orientation_
        ? ACCELEROMETER_MAGNETIC : ROTATION_VECTOR);
  }
}

void SensorManagerAndroid::GotAcceleration(
    JNIEnv*, jobject, double x, double y, double z) {
  base::AutoLock autolock(motion_buffer_lock_);

  if (!device_motion_buffer_)
    return;

  device_motion_buffer_->seqlock.WriteBegin();
  device_motion_buffer_->data.accelerationX = x;
  device_motion_buffer_->data.hasAccelerationX = true;
  device_motion_buffer_->data.accelerationY = y;
  device_motion_buffer_->data.hasAccelerationY = true;
  device_motion_buffer_->data.accelerationZ = z;
  device_motion_buffer_->data.hasAccelerationZ = true;
  device_motion_buffer_->seqlock.WriteEnd();

  if (!is_motion_buffer_ready_) {
    received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION] = 1;
    CheckMotionBufferReadyToRead();
  }
}

void SensorManagerAndroid::GotAccelerationIncludingGravity(
    JNIEnv*, jobject, double x, double y, double z) {
  base::AutoLock autolock(motion_buffer_lock_);

  if (!device_motion_buffer_)
    return;

  device_motion_buffer_->seqlock.WriteBegin();
  device_motion_buffer_->data.accelerationIncludingGravityX = x;
  device_motion_buffer_->data.hasAccelerationIncludingGravityX = true;
  device_motion_buffer_->data.accelerationIncludingGravityY = y;
  device_motion_buffer_->data.hasAccelerationIncludingGravityY = true;
  device_motion_buffer_->data.accelerationIncludingGravityZ = z;
  device_motion_buffer_->data.hasAccelerationIncludingGravityZ = true;
  device_motion_buffer_->seqlock.WriteEnd();

  if (!is_motion_buffer_ready_) {
    received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY] = 1;
    CheckMotionBufferReadyToRead();
  }
}

void SensorManagerAndroid::GotRotationRate(
    JNIEnv*, jobject, double alpha, double beta, double gamma) {
  base::AutoLock autolock(motion_buffer_lock_);

  if (!device_motion_buffer_)
    return;

  device_motion_buffer_->seqlock.WriteBegin();
  device_motion_buffer_->data.rotationRateAlpha = alpha;
  device_motion_buffer_->data.hasRotationRateAlpha = true;
  device_motion_buffer_->data.rotationRateBeta = beta;
  device_motion_buffer_->data.hasRotationRateBeta = true;
  device_motion_buffer_->data.rotationRateGamma = gamma;
  device_motion_buffer_->data.hasRotationRateGamma = true;
  device_motion_buffer_->seqlock.WriteEnd();

  if (!is_motion_buffer_ready_) {
    received_motion_data_[RECEIVED_MOTION_DATA_ROTATION_RATE] = 1;
    CheckMotionBufferReadyToRead();
  }
}

void SensorManagerAndroid::GotLight(JNIEnv*, jobject, double value) {
  base::AutoLock autolock(light_buffer_lock_);

  if (!device_light_buffer_)
    return;

  device_light_buffer_->seqlock.WriteBegin();
  device_light_buffer_->data.value = value;
  device_light_buffer_->seqlock.WriteEnd();
}

bool SensorManagerAndroid::Start(EventType event_type) {
  DCHECK(!device_sensors_.is_null());
  int rate_in_microseconds = (event_type == kTypeLight)
                                 ? kLightSensorIntervalMicroseconds
                                 : kInertialSensorIntervalMicroseconds;
  return Java_DeviceSensors_start(AttachCurrentThread(),
                                  device_sensors_.obj(),
                                  reinterpret_cast<intptr_t>(this),
                                  static_cast<jint>(event_type),
                                  rate_in_microseconds);
}

void SensorManagerAndroid::Stop(EventType event_type) {
  DCHECK(!device_sensors_.is_null());
  Java_DeviceSensors_stop(AttachCurrentThread(),
                          device_sensors_.obj(),
                          static_cast<jint>(event_type));
}

int SensorManagerAndroid::GetNumberActiveDeviceMotionSensors() {
  DCHECK(!device_sensors_.is_null());
  return Java_DeviceSensors_getNumberActiveDeviceMotionSensors(
      AttachCurrentThread(), device_sensors_.obj());
}

bool SensorManagerAndroid::isUsingBackupSensorsForOrientation() {
  DCHECK(!device_sensors_.is_null());
  return Java_DeviceSensors_isUsingBackupSensorsForOrientation(
      AttachCurrentThread(), device_sensors_.obj());
}

// ----- Shared memory API methods

// --- Device Light

bool SensorManagerAndroid::StartFetchingDeviceLightData(
    DeviceLightHardwareBuffer* buffer) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StartFetchingLightDataOnUI(buffer);
  } else {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&SensorManagerAndroid::StartFetchingLightDataOnUI,
                   base::Unretained(this),
                   buffer));
  }
  return true;
}

void SensorManagerAndroid::StartFetchingLightDataOnUI(
    DeviceLightHardwareBuffer* buffer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(buffer);
  if (is_shutdown_)
    return;

  {
    base::AutoLock autolock(light_buffer_lock_);
    device_light_buffer_ = buffer;
    SetLightBufferValue(-1);
  }
  bool success = Start(kTypeLight);
  if (!success) {
    base::AutoLock autolock(light_buffer_lock_);
    SetLightBufferValue(std::numeric_limits<double>::infinity());
  }
}

void SensorManagerAndroid::StopFetchingDeviceLightData() {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StopFetchingLightDataOnUI();
    return;
  }

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&SensorManagerAndroid::StopFetchingLightDataOnUI,
                 base::Unretained(this)));
}

void SensorManagerAndroid::StopFetchingLightDataOnUI() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (is_shutdown_)
    return;

  Stop(kTypeLight);
  {
    base::AutoLock autolock(light_buffer_lock_);
    if (device_light_buffer_) {
      SetLightBufferValue(-1);
      device_light_buffer_ = nullptr;
    }
  }
}

void SensorManagerAndroid::SetLightBufferValue(double lux) {
  device_light_buffer_->seqlock.WriteBegin();
  device_light_buffer_->data.value = lux;
  device_light_buffer_->seqlock.WriteEnd();
}

// --- Device Motion

bool SensorManagerAndroid::StartFetchingDeviceMotionData(
    DeviceMotionHardwareBuffer* buffer) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StartFetchingMotionDataOnUI(buffer);
  } else {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&SensorManagerAndroid::StartFetchingMotionDataOnUI,
                   base::Unretained(this),
                   buffer));
  }
  return true;
}

void SensorManagerAndroid::StartFetchingMotionDataOnUI(
    DeviceMotionHardwareBuffer* buffer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(buffer);
  if (is_shutdown_)
    return;

  {
    base::AutoLock autolock(motion_buffer_lock_);
    device_motion_buffer_ = buffer;
    ClearInternalMotionBuffers();
  }
  Start(kTypeMotion);

  // If no motion data can ever be provided, the number of active device motion
  // sensors will be zero. In that case flag the shared memory buffer
  // as ready to read, as it will not change anyway.
  number_active_device_motion_sensors_ = GetNumberActiveDeviceMotionSensors();
  {
    base::AutoLock autolock(motion_buffer_lock_);
    CheckMotionBufferReadyToRead();
  }
}

void SensorManagerAndroid::StopFetchingDeviceMotionData() {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StopFetchingMotionDataOnUI();
    return;
  }

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&SensorManagerAndroid::StopFetchingMotionDataOnUI,
                 base::Unretained(this)));
}

void SensorManagerAndroid::StopFetchingMotionDataOnUI() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (is_shutdown_)
    return;

  Stop(kTypeMotion);
  {
    base::AutoLock autolock(motion_buffer_lock_);
    if (device_motion_buffer_) {
      ClearInternalMotionBuffers();
      device_motion_buffer_ = nullptr;
    }
  }
}

void SensorManagerAndroid::CheckMotionBufferReadyToRead() {
  if (received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION] +
      received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY] +
      received_motion_data_[RECEIVED_MOTION_DATA_ROTATION_RATE] ==
      number_active_device_motion_sensors_) {
    device_motion_buffer_->seqlock.WriteBegin();
    device_motion_buffer_->data.interval =
        kInertialSensorIntervalMicroseconds / 1000.;
    device_motion_buffer_->seqlock.WriteEnd();
    SetMotionBufferReadyStatus(true);

    UMA_HISTOGRAM_BOOLEAN("InertialSensor.AccelerometerAndroidAvailable",
        received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION] > 0);
    UMA_HISTOGRAM_BOOLEAN(
        "InertialSensor.AccelerometerIncGravityAndroidAvailable",
        received_motion_data_[RECEIVED_MOTION_DATA_ACCELERATION_INCL_GRAVITY]
        > 0);
    UMA_HISTOGRAM_BOOLEAN("InertialSensor.GyroscopeAndroidAvailable",
        received_motion_data_[RECEIVED_MOTION_DATA_ROTATION_RATE] > 0);
  }
}

void SensorManagerAndroid::SetMotionBufferReadyStatus(bool ready) {
  device_motion_buffer_->seqlock.WriteBegin();
  device_motion_buffer_->data.allAvailableSensorsAreActive = ready;
  device_motion_buffer_->seqlock.WriteEnd();
  is_motion_buffer_ready_ = ready;
}

void SensorManagerAndroid::ClearInternalMotionBuffers() {
  memset(received_motion_data_, 0, sizeof(received_motion_data_));
  number_active_device_motion_sensors_ = 0;
  SetMotionBufferReadyStatus(false);
}

// --- Device Orientation

void SensorManagerAndroid::SetOrientationBufferReadyStatus(bool ready) {
  device_orientation_buffer_->seqlock.WriteBegin();
  device_orientation_buffer_->data.absolute = ready;
  device_orientation_buffer_->data.hasAbsolute = ready;
  device_orientation_buffer_->data.allAvailableSensorsAreActive = ready;
  device_orientation_buffer_->seqlock.WriteEnd();
  is_orientation_buffer_ready_ = ready;
}

bool SensorManagerAndroid::StartFetchingDeviceOrientationData(
    DeviceOrientationHardwareBuffer* buffer) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StartFetchingOrientationDataOnUI(buffer);
  } else {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&SensorManagerAndroid::StartFetchingOrientationDataOnUI,
                   base::Unretained(this),
                   buffer));
  }
  return true;
}

void SensorManagerAndroid::StartFetchingOrientationDataOnUI(
    DeviceOrientationHardwareBuffer* buffer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(buffer);
  if (is_shutdown_)
    return;

  {
    base::AutoLock autolock(orientation_buffer_lock_);
    device_orientation_buffer_ = buffer;
  }
  bool success = Start(kTypeOrientation);

  {
    base::AutoLock autolock(orientation_buffer_lock_);
    // If Start() was unsuccessful then set the buffer ready flag to true
    // to start firing all-null events.
    SetOrientationBufferReadyStatus(!success);
  }

  if (!success) {
    UpdateDeviceOrientationHistogram(NOT_AVAILABLE);
  } else {
    is_using_backup_sensors_for_orientation_ =
        isUsingBackupSensorsForOrientation();
  }
}

void SensorManagerAndroid::StopFetchingDeviceOrientationData() {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    StopFetchingOrientationDataOnUI();
    return;
  }

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&SensorManagerAndroid::StopFetchingOrientationDataOnUI,
                 base::Unretained(this)));
}

void SensorManagerAndroid::StopFetchingOrientationDataOnUI() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (is_shutdown_)
    return;

  Stop(kTypeOrientation);
  {
    base::AutoLock autolock(orientation_buffer_lock_);
    if (device_orientation_buffer_) {
      SetOrientationBufferReadyStatus(false);
      device_orientation_buffer_ = nullptr;
    }
  }
}

void SensorManagerAndroid::Shutdown() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  is_shutdown_ = true;
}

}  // namespace content