Updating shake handling for Android in ShakeDetector and DevSupportManagerImpl

Summary: If you use a ShakeDetector, you can specify the minimum number of shakes required to trigger a shake handler.  Otherwise, the minimum number of required shakes is set to 1 by default.

Reviewed By: achen1

Differential Revision: D5155604

fbshipit-source-id: 5073fa37d4c223eb18e85b5e850b95d37136e3d2
This commit is contained in:
Summer Kitahara
2017-06-05 21:25:09 -07:00
committed by Facebook Github Bot
parent 9e026ec358
commit aeccbd6906
5 changed files with 92 additions and 39 deletions

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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() {