diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 2ea99c030..85eed009e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -150,6 +150,7 @@ public class ReactInstanceManager { private final boolean mLazyViewManagersEnabled; private final boolean mSetupReactContextInBackgroundEnabled; private final boolean mUseSeparateUIBackgroundThread; + private final int mMinNumShakes; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -222,7 +223,8 @@ public class ReactInstanceManager { boolean lazyNativeModulesEnabled, boolean lazyViewManagersEnabled, boolean setupReactContextInBackgroundEnabled, - boolean useSeparateUIBackgroundThread) { + boolean useSeparateUIBackgroundThread, + int minNumShakes) { initializeSoLoaderIfNecessary(applicationContext); @@ -240,7 +242,8 @@ public class ReactInstanceManager { mDevInterface, mJSMainModuleName, useDeveloperSupport, - redBoxHandler); + redBoxHandler, + minNumShakes); mBridgeIdleDebugListener = bridgeIdleDebugListener; mLifecycleState = initialLifecycleState; mUIImplementationProvider = uiImplementationProvider; @@ -251,6 +254,7 @@ public class ReactInstanceManager { mLazyViewManagersEnabled = lazyViewManagersEnabled; mSetupReactContextInBackgroundEnabled = setupReactContextInBackgroundEnabled; mUseSeparateUIBackgroundThread = useSeparateUIBackgroundThread; + mMinNumShakes = minNumShakes; // Instantiate ReactChoreographer in UI thread. ReactChoreographer.initialize(); @@ -685,6 +689,10 @@ public class ReactInstanceManager { return mLifecycleState; } + public int getMinNumShakes() { + return mMinNumShakes; + } + @ThreadConfined(UI) private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { synchronized (mAttachedRootViews) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index 322bf7cd7..3ceac7ebe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -44,6 +44,7 @@ public class ReactInstanceManagerBuilder { protected boolean mLazyViewManagersEnabled; protected boolean mSetupReactContextInBackground; protected boolean mUseSeparateUIBackgroundThread; + protected int mMinNumShakes = 1; /* package protected */ ReactInstanceManagerBuilder() { } @@ -200,6 +201,11 @@ public class ReactInstanceManagerBuilder { return this; } + public ReactInstanceManagerBuilder setMinNumShakes(int minNumShakes) { + mMinNumShakes = minNumShakes; + return this; + } + /** * Instantiates a new {@link ReactInstanceManager}. * Before calling {@code build}, the following must be called: @@ -247,6 +253,7 @@ public class ReactInstanceManagerBuilder { mLazyNativeModulesEnabled, mLazyViewManagersEnabled, mSetupReactContextInBackground, - mUseSeparateUIBackgroundThread); + mUseSeparateUIBackgroundThread, + mMinNumShakes); } } 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 0197980e7..59f7c0456 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/ShakeDetector.java @@ -11,6 +11,8 @@ package com.facebook.react.common; import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -23,11 +25,21 @@ import com.facebook.infer.annotation.Assertions; */ 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; + //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) + 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; - private static final int PERCENT_OVER_THRESHOLD_FOR_SHAKE = 66; + //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 + private static final float SHAKING_WINDOW_NS = + TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS); public static interface ShakeListener { void onShake(); @@ -38,11 +50,20 @@ 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; public ShakeDetector(ShakeListener listener) { + this(listener, 1); + } + + public ShakeDetector(ShakeListener listener, int minNumShakes) { mShakeListener = listener; + mMinNumShakes = minNumShakes; } /** @@ -57,8 +78,9 @@ public class ShakeDetector implements SensorEventListener { mCurrentIndex = 0; mMagnitudes = new double[MAX_SAMPLES]; mTimestamps = new long[MAX_SAMPLES]; - mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI); + mNumShakes = 0; + mLastShakeTimestamp = 0; } } @@ -74,7 +96,7 @@ public class ShakeDetector implements SensorEventListener { @Override public void onSensorChanged(SensorEvent sensorEvent) { - if (sensorEvent.timestamp - mLastTimestamp < MIN_TIME_BETWEEN_SAMPLES_MS) { + if (sensorEvent.timestamp - mLastTimestamp < MIN_TIME_BETWEEN_SAMPLES_NS) { return; } @@ -106,16 +128,27 @@ public class ShakeDetector implements SensorEventListener { 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) { + 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) { - mShakeListener.onShake(); + 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; } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java index e3ba2e6b4..6301bc717 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -32,14 +32,16 @@ public class DevSupportManagerFactory { Context applicationContext, ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate) { + boolean enableOnCreate, + int minNumShakes) { return create( applicationContext, reactInstanceCommandsHandler, packagerPathForJSBundleName, enableOnCreate, - null); + null, + minNumShakes); } public static DevSupportManager create( @@ -47,7 +49,8 @@ public class DevSupportManagerFactory { ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler) { + @Nullable RedBoxHandler redBoxHandler, + int minNumShakes) { if (!enableOnCreate) { return new DisabledDevSupportManager(); } @@ -68,13 +71,15 @@ public class DevSupportManagerFactory { ReactInstanceDevCommandsHandler.class, String.class, boolean.class, - RedBoxHandler.class); + RedBoxHandler.class, + int.class); return (DevSupportManager) constructor.newInstance( applicationContext, reactInstanceCommandsHandler, packagerPathForJSBundleName, true, - redBoxHandler); + redBoxHandler, + minNumShakes); } catch (Exception e) { throw new RuntimeException( "Requested enabled DevSupportManager, but DevSupportManagerImpl class was not found" + @@ -82,5 +87,4 @@ public class DevSupportManagerFactory { e); } } - } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index a9e4d9c21..4f0b422d3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -9,6 +9,19 @@ package com.facebook.react.devsupport; +import javax.annotation.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import android.app.ActivityManager; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -34,8 +47,8 @@ import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.ReactConstants; import com.facebook.react.common.DebugServerException; +import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ShakeDetector; import com.facebook.react.common.futures.SimpleSettableFuture; import com.facebook.react.devsupport.DevServerHelper.PackagerCommandListener; @@ -44,22 +57,8 @@ import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; import com.facebook.react.devsupport.interfaces.StackFrame; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; -import com.facebook.react.packagerconnection.JSPackagerClient; -import com.facebook.react.packagerconnection.Responder; import com.facebook.react.packagerconnection.RequestHandler; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.annotation.Nullable; +import com.facebook.react.packagerconnection.Responder; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -172,13 +171,15 @@ public class DevSupportManagerImpl implements Context applicationContext, ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate) { + boolean enableOnCreate, + int minNumShakes) { this(applicationContext, reactInstanceCommandsHandler, packagerPathForJSBundleName, enableOnCreate, - null); + null, + minNumShakes); } public DevSupportManagerImpl( @@ -186,8 +187,8 @@ public class DevSupportManagerImpl implements ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler) { - + @Nullable RedBoxHandler redBoxHandler, + int minNumShakes) { mReactInstanceCommandsHandler = reactInstanceCommandsHandler; mApplicationContext = applicationContext; mJSAppBundleName = packagerPathForJSBundleName; @@ -200,7 +201,7 @@ public class DevSupportManagerImpl implements public void onShake() { showDevOptionsDialog(); } - }); + }, minNumShakes); // Prepare reload APP broadcast receiver (will be registered/unregistered from #reload) mReloadAppBroadcastReceiver = new BroadcastReceiver() {