diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java b/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java index 59f7c0456..b3388bdbd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java @@ -24,22 +24,17 @@ import com.facebook.infer.annotation.Assertions; * Listens for the user shaking their phone. Allocation-less once it starts listening. */ public class ShakeDetector implements SensorEventListener { - - //only record and consider the last MAX_SAMPLES number of data points - private static final int MAX_SAMPLES = 40; - //collect sensor data in this interval (nanoseconds) + // Collect sensor data in this interval (nanoseconds) private static final long MIN_TIME_BETWEEN_SAMPLES_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS); - //expected duration of one shake in nanoseconds - private static final long VISIBLE_TIME_RANGE_NS = - TimeUnit.NANOSECONDS.convert(250, TimeUnit.MILLISECONDS); - //minimum amount of force on accelerometer sensor to constitute a shake - private static final int MAGNITUDE_THRESHOLD = 25; - //this percentage of data points must have at least the force of MAGNITUDE_THRESHOLD - private static final int PERCENT_OVER_THRESHOLD_FOR_SHAKE = 60; - //number of nanoseconds to listen for and count shakes + // Number of nanoseconds to listen for and count shakes (nanoseconds) private static final float SHAKING_WINDOW_NS = TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS); + // Required force to constitute a rage shake. Need to multiply gravity by 1.33 because a rage + // shake in one direction should have more force than just the magnitude of free fall. + private static final float REQUIRED_FORCE = SensorManager.GRAVITY_EARTH * 1.33f; + + private float mAccelerationX, mAccelerationY, mAccelerationZ; public static interface ShakeListener { void onShake(); @@ -49,11 +44,8 @@ public class ShakeDetector implements SensorEventListener { @Nullable private SensorManager mSensorManager; private long mLastTimestamp; - private int mCurrentIndex; private int mNumShakes; private long mLastShakeTimestamp; - @Nullable private double[] mMagnitudes; - @Nullable private long[] mTimestamps; //number of shakes required to trigger onShake() private int mMinNumShakes; @@ -75,12 +67,9 @@ public class ShakeDetector implements SensorEventListener { 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); - mNumShakes = 0; mLastShakeTimestamp = 0; + reset(); } } @@ -94,26 +83,60 @@ public class ShakeDetector implements SensorEventListener { } } + /** + * Reset all variables used to keep track of number of shakes recorded. + */ + private void reset() { + mNumShakes = 0; + mAccelerationX = 0; + mAccelerationY = 0; + mAccelerationZ = 0; + } + + /** + * Determine if acceleration applied to sensor is large enough to count as a rage shake. + * + * @param a acceleration in x, y, or z applied to the sensor + * @return true if the magnitude of the force exceeds the minimum required amount of force. + * false otherwise. + */ + private boolean atLeastRequiredForce(float a) { + return Math.abs(a) > REQUIRED_FORCE; + } + + /** + * Save data about last shake + * @param timestamp (ns) of last sensor event + */ + private void recordShake(long timestamp) { + mLastShakeTimestamp = timestamp; + mNumShakes++; + } + @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.timestamp - mLastTimestamp < MIN_TIME_BETWEEN_SAMPLES_NS) { return; } - Assertions.assertNotNull(mTimestamps); - Assertions.assertNotNull(mMagnitudes); - float ax = sensorEvent.values[0]; float ay = sensorEvent.values[1]; - float az = sensorEvent.values[2]; + float az = sensorEvent.values[2] - SensorManager.GRAVITY_EARTH; mLastTimestamp = sensorEvent.timestamp; - mTimestamps[mCurrentIndex] = sensorEvent.timestamp; - mMagnitudes[mCurrentIndex] = Math.sqrt(ax * ax + ay * ay + az * az); + + if (atLeastRequiredForce(ax) && ax * mAccelerationX <= 0) { + recordShake(sensorEvent.timestamp); + mAccelerationX = ax; + } else if (atLeastRequiredForce(ay) && ay * mAccelerationY <= 0) { + recordShake(sensorEvent.timestamp); + mAccelerationY = ay; + } else if (atLeastRequiredForce(az) && az * mAccelerationZ <= 0) { + recordShake(sensorEvent.timestamp); + mAccelerationZ = az; + } maybeDispatchShake(sensorEvent.timestamp); - - mCurrentIndex = (mCurrentIndex + 1) % MAX_SAMPLES; } @Override @@ -121,34 +144,13 @@ public class ShakeDetector implements SensorEventListener { } private void maybeDispatchShake(long currentTimestamp) { - Assertions.assertNotNull(mTimestamps); - Assertions.assertNotNull(mMagnitudes); + if (mNumShakes >= 8 * mMinNumShakes) { + reset(); + mShakeListener.onShake(); + } - 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_NS) { - total++; - if (mMagnitudes[index] >= MAGNITUDE_THRESHOLD) { - numOverThreshold++; - } - } - } - if (((double) numOverThreshold) / total > PERCENT_OVER_THRESHOLD_FOR_SHAKE / 100.0) { - if (currentTimestamp - mLastShakeTimestamp >= VISIBLE_TIME_RANGE_NS) { - mNumShakes++; - } - mLastShakeTimestamp = currentTimestamp; - if (mNumShakes >= mMinNumShakes) { - mNumShakes = 0; - mLastShakeTimestamp = 0; - mShakeListener.onShake(); - } - } if (currentTimestamp - mLastShakeTimestamp > SHAKING_WINDOW_NS) { - mNumShakes = 0; - mLastShakeTimestamp = 0; + reset(); } } }