Skip to content

Commit

Permalink
Updating shake handling for Android in ShakeDetector and DevSupportMa…
Browse files Browse the repository at this point in the history
…nagerImpl

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
  • Loading branch information
sumkit authored and facebook-github-bot committed Jun 6, 2017
1 parent 9e026ec commit aeccbd6
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public interface ReactInstanceEventListener {
private final boolean mLazyViewManagersEnabled;
private final boolean mSetupReactContextInBackgroundEnabled;
private final boolean mUseSeparateUIBackgroundThread;
private final int mMinNumShakes;

private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() {
Expand Down Expand Up @@ -222,7 +223,8 @@ public static ReactInstanceManagerBuilder builder() {
boolean lazyNativeModulesEnabled,
boolean lazyViewManagersEnabled,
boolean setupReactContextInBackgroundEnabled,
boolean useSeparateUIBackgroundThread) {
boolean useSeparateUIBackgroundThread,
int minNumShakes) {

initializeSoLoaderIfNecessary(applicationContext);

Expand All @@ -240,7 +242,8 @@ public static ReactInstanceManagerBuilder builder() {
mDevInterface,
mJSMainModuleName,
useDeveloperSupport,
redBoxHandler);
redBoxHandler,
minNumShakes);
mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState;
mUIImplementationProvider = uiImplementationProvider;
Expand All @@ -251,6 +254,7 @@ public static ReactInstanceManagerBuilder builder() {
mLazyViewManagersEnabled = lazyViewManagersEnabled;
mSetupReactContextInBackgroundEnabled = setupReactContextInBackgroundEnabled;
mUseSeparateUIBackgroundThread = useSeparateUIBackgroundThread;
mMinNumShakes = minNumShakes;

// Instantiate ReactChoreographer in UI thread.
ReactChoreographer.initialize();
Expand Down Expand Up @@ -685,6 +689,10 @@ public LifecycleState getLifecycleState() {
return mLifecycleState;
}

public int getMinNumShakes() {
return mMinNumShakes;
}

@ThreadConfined(UI)
private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
synchronized (mAttachedRootViews) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class ReactInstanceManagerBuilder {
protected boolean mLazyViewManagersEnabled;
protected boolean mSetupReactContextInBackground;
protected boolean mUseSeparateUIBackgroundThread;
protected int mMinNumShakes = 1;

/* package protected */ ReactInstanceManagerBuilder() {
}
Expand Down Expand Up @@ -200,6 +201,11 @@ public ReactInstanceManagerBuilder setUseSeparateUIBackgroundThread(
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:
Expand Down Expand Up @@ -247,6 +253,7 @@ public ReactInstanceManager build() {
mLazyNativeModulesEnabled,
mLazyViewManagersEnabled,
mSetupReactContextInBackground,
mUseSeparateUIBackgroundThread);
mUseSeparateUIBackgroundThread,
mMinNumShakes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import javax.annotation.Nullable;

import java.util.concurrent.TimeUnit;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
Expand All @@ -23,11 +25,21 @@
*/
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();
Expand All @@ -38,11 +50,20 @@ public static interface ShakeListener {
@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;
}

/**
Expand All @@ -57,8 +78,9 @@ public void start(SensorManager manager) {
mCurrentIndex = 0;
mMagnitudes = new double[MAX_SAMPLES];
mTimestamps = new long[MAX_SAMPLES];

mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
mNumShakes = 0;
mLastShakeTimestamp = 0;
}
}

Expand All @@ -74,7 +96,7 @@ public void stop() {

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

Expand Down Expand Up @@ -106,16 +128,27 @@ private void maybeDispatchShake(long currentTimestamp) {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,25 @@ public static DevSupportManager create(
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(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler) {
@Nullable RedBoxHandler redBoxHandler,
int minNumShakes) {
if (!enableOnCreate) {
return new DisabledDevSupportManager();
}
Expand All @@ -68,19 +71,20 @@ public static DevSupportManager create(
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" +
" or could not be created",
e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,8 +47,8 @@
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;
Expand All @@ -44,22 +57,8 @@
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;
Expand Down Expand Up @@ -172,22 +171,24 @@ public DevSupportManagerImpl(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate) {
boolean enableOnCreate,
int minNumShakes) {

this(applicationContext,
reactInstanceCommandsHandler,
packagerPathForJSBundleName,
enableOnCreate,
null);
null,
minNumShakes);
}

public DevSupportManagerImpl(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler) {

@Nullable RedBoxHandler redBoxHandler,
int minNumShakes) {
mReactInstanceCommandsHandler = reactInstanceCommandsHandler;
mApplicationContext = applicationContext;
mJSAppBundleName = packagerPathForJSBundleName;
Expand All @@ -200,7 +201,7 @@ public DevSupportManagerImpl(
public void onShake() {
showDevOptionsDialog();
}
});
}, minNumShakes);

// Prepare reload APP broadcast receiver (will be registered/unregistered from #reload)
mReloadAppBroadcastReceiver = new BroadcastReceiver() {
Expand Down

0 comments on commit aeccbd6

Please sign in to comment.