From 2f651c878393f75dbfa81f260ec10de55f97c783 Mon Sep 17 00:00:00 2001 From: Thomas Nardone Date: Wed, 31 Jul 2024 11:28:07 -0700 Subject: [PATCH] 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 --- .../ReactAndroid/api/ReactAndroid.api | 7 +- .../facebook/react/common/ShakeDetector.java | 144 ------------------ .../facebook/react/common/ShakeDetector.kt | 124 +++++++++++++++ 3 files changed, 128 insertions(+), 147 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8c2cd57e6bb..6f0254bef44 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 (Lcom/facebook/react/common/ShakeDetector$ShakeListener;)V public fun (Lcom/facebook/react/common/ShakeDetector$ShakeListener;I)V + public synthetic fun (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 { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java deleted file mode 100644 index 9979e9dea08..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java +++ /dev/null @@ -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(); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.kt new file mode 100644 index 00000000000..9624751e90f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.kt @@ -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