Convert common.ShakeDetector (#45744)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45744

Changelog: [Internal]

Reviewed By: rshest

Differential Revision: D60311462

fbshipit-source-id: 142649a18b365b0954dd138c895030fad4780ed1
This commit is contained in:
Thomas Nardone
2024-07-31 11:28:07 -07:00
committed by Facebook GitHub Bot
parent 2962d69b67
commit 2f651c8783
3 changed files with 128 additions and 147 deletions
@@ -1830,13 +1830,14 @@ public final class com/facebook/react/common/ReactConstants {
public static final field UNSET I
}
public class com/facebook/react/common/ShakeDetector : android/hardware/SensorEventListener {
public final class com/facebook/react/common/ShakeDetector : android/hardware/SensorEventListener {
public fun <init> (Lcom/facebook/react/common/ShakeDetector$ShakeListener;)V
public fun <init> (Lcom/facebook/react/common/ShakeDetector$ShakeListener;I)V
public synthetic fun <init> (Lcom/facebook/react/common/ShakeDetector$ShakeListener;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onAccuracyChanged (Landroid/hardware/Sensor;I)V
public fun onSensorChanged (Landroid/hardware/SensorEvent;)V
public fun start (Landroid/hardware/SensorManager;)V
public fun stop ()V
public final fun start (Landroid/hardware/SensorManager;)V
public final fun stop ()V
}
public abstract interface class com/facebook/react/common/ShakeDetector$ShakeListener {
@@ -1,144 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.common;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import java.util.concurrent.TimeUnit;
/** Listens for the user shaking their phone. Allocation-less once it starts listening. */
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ShakeDetector implements SensorEventListener {
// Collect sensor data in this interval (nanoseconds)
private static final long MIN_TIME_BETWEEN_SAMPLES_NS =
TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS);
// 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 interface ShakeListener {
void onShake();
}
private final ShakeListener mShakeListener;
@Nullable private SensorManager mSensorManager;
private long mLastTimestamp;
private int mNumShakes;
private long mLastShakeTimestamp;
// number of shakes required to trigger onShake()
private int mMinNumShakes;
public ShakeDetector(ShakeListener listener) {
this(listener, 1);
}
public ShakeDetector(ShakeListener listener, int minNumShakes) {
mShakeListener = listener;
mMinNumShakes = minNumShakes;
}
/** 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;
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
mLastShakeTimestamp = 0;
reset();
}
}
/** Stop listening for shakes. */
public void stop() {
if (mSensorManager != null) {
mSensorManager.unregisterListener(this);
mSensorManager = null;
}
}
/** 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;
}
float ax = sensorEvent.values[0];
float ay = sensorEvent.values[1];
float az = sensorEvent.values[2] - SensorManager.GRAVITY_EARTH;
mLastTimestamp = sensorEvent.timestamp;
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);
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {}
private void maybeDispatchShake(long currentTimestamp) {
if (mNumShakes >= 8 * mMinNumShakes) {
reset();
mShakeListener.onShake();
}
if (currentTimestamp - mLastShakeTimestamp > SHAKING_WINDOW_NS) {
reset();
}
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.common
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import java.util.concurrent.TimeUnit
/** Listens for the user shaking their phone. Allocation-less once it starts listening. */
public class ShakeDetector
@JvmOverloads
constructor(private val shakeListener: ShakeListener, private val minNumShakes: Int = 1) :
SensorEventListener {
private var accelerationX = 0f
private var accelerationY = 0f
private var accelerationZ = 0f
public fun interface ShakeListener {
public fun onShake()
}
private var sensorManager: SensorManager? = null
private var lastTimestamp: Long = 0
private var numShakes = 0
private var lastShakeTimestamp: Long = 0
/** Start listening for shakes. */
public fun start(manager: SensorManager): Unit {
val accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) ?: return
sensorManager = manager
lastTimestamp = -1
manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
lastShakeTimestamp = 0
reset()
}
/** Stop listening for shakes. */
public fun stop(): Unit {
sensorManager?.unregisterListener(this)
sensorManager = null
}
/** Reset all variables used to keep track of number of shakes recorded. */
private fun reset() {
numShakes = 0
accelerationX = 0f
accelerationY = 0f
accelerationZ = 0f
}
/**
* 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 fun atLeastRequiredForce(a: Float): Boolean = Math.abs(a) > REQUIRED_FORCE
/**
* Save data about last shake
*
* @param timestamp (ns) of last sensor event
*/
private fun recordShake(timestamp: Long) {
lastShakeTimestamp = timestamp
numShakes++
}
override fun onSensorChanged(sensorEvent: SensorEvent) {
if (sensorEvent.timestamp - lastTimestamp < MIN_TIME_BETWEEN_SAMPLES_NS) {
return
}
val ax = sensorEvent.values[0]
val ay = sensorEvent.values[1]
val az = sensorEvent.values[2] - SensorManager.GRAVITY_EARTH
lastTimestamp = sensorEvent.timestamp
when {
atLeastRequiredForce(ax) && ax * accelerationX <= 0 -> {
recordShake(sensorEvent.timestamp)
accelerationX = ax
}
atLeastRequiredForce(ay) && ay * accelerationY <= 0 -> {
recordShake(sensorEvent.timestamp)
accelerationY = ay
}
atLeastRequiredForce(az) && az * accelerationZ <= 0 -> {
recordShake(sensorEvent.timestamp)
accelerationZ = az
}
}
maybeDispatchShake(sensorEvent.timestamp)
}
override fun onAccuracyChanged(sensor: Sensor, i: Int): Unit = Unit
private fun maybeDispatchShake(currentTimestamp: Long) {
if (numShakes >= 8 * minNumShakes) {
reset()
shakeListener.onShake()
}
if (currentTimestamp - lastShakeTimestamp > SHAKING_WINDOW_NS) {
reset()
}
}
}
// Collect sensor data in this interval (nanoseconds)
private val MIN_TIME_BETWEEN_SAMPLES_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS)
// Number of nanoseconds to listen for and count shakes (nanoseconds)
private val SHAKING_WINDOW_NS = TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS).toFloat()
// 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 const val REQUIRED_FORCE = SensorManager.GRAVITY_EARTH * 1.33f