/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.common;
import javax.annotation.Nullable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import com.facebook.infer.annotation.Assertions;
/**
* Listens for the user shaking their phone. Allocation-less once it starts listening.
*/
public class ShakeDetector implements SensorEventListener {
private static final int MAX_SAMPLES = 25;
private static final int MIN_TIME_BETWEEN_SAMPLES_MS = 20;
private static final int VISIBLE_TIME_RANGE_MS = 500;
private static final int MAGNITUDE_THRESHOLD = 25;
private static final int PERCENT_OVER_THRESHOLD_FOR_SHAKE = 66;
public static interface ShakeListener {
void onShake();
}
private final ShakeListener mShakeListener;
@Nullable private SensorManager mSensorManager;
private long mLastTimestamp;
private int mCurrentIndex;
@Nullable private double[] mMagnitudes;
@Nullable private long[] mTimestamps;
public ShakeDetector(ShakeListener listener) {
mShakeListener = listener;
}
/**
* Start listening for shakes.
*/
public void start(SensorManager manager) {
Assertions.assertNotNull(manager);
Sensor accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
mSensorManager = manager;
mLastTimestamp = -1;
mCurrentIndex = 0;
mMagnitudes = new double[MAX_SAMPLES];
mTimestamps = new long[MAX_SAMPLES];
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
}
}
/**
* Stop listening for shakes.
*/
public void stop() {
if (mSensorManager != null) {
mSensorManager.unregisterListener(this);
mSensorManager = null;
}
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.timestamp - mLastTimestamp < MIN_TIME_BETWEEN_SAMPLES_MS) {
return;
}
Assertions.assertNotNull(mTimestamps);
Assertions.assertNotNull(mMagnitudes);
float ax = sensorEvent.values[0];
float ay = sensorEvent.values[1];
float az = sensorEvent.values[2];
mLastTimestamp = sensorEvent.timestamp;
mTimestamps[mCurrentIndex] = sensorEvent.timestamp;
mMagnitudes[mCurrentIndex] = Math.sqrt(ax * ax + ay * ay + az * az);
maybeDispatchShake(sensorEvent.timestamp);
mCurrentIndex = (mCurrentIndex + 1) % MAX_SAMPLES;
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
private void maybeDispatchShake(long currentTimestamp) {
Assertions.assertNotNull(mTimestamps);
Assertions.assertNotNull(mMagnitudes);
int numOverThreshold = 0;
int total = 0;
for (int i = 0; i < MAX_SAMPLES; i++) {
int index = (mCurrentIndex - i + MAX_SAMPLES) % MAX_SAMPLES;
if (currentTimestamp - mTimestamps[index] < VISIBLE_TIME_RANGE_MS) {
total++;
if (mMagnitudes[index] >= MAGNITUDE_THRESHOLD) {
numOverThreshold++;
}
}
}
if (((double) numOverThreshold) / total > PERCENT_OVER_THRESHOLD_FOR_SHAKE / 100.0) {
mShakeListener.onShake();
}
}
}