package com.mixpanel.android.viewcrawler;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import com.mixpanel.android.util.MPLog;
/* package */ class FlipGesture implements SensorEventListener {
public interface OnFlipGestureListener {
public void onFlipGesture();
}
public FlipGesture(OnFlipGestureListener listener) {
mListener = listener;
}
@Override
public void onSensorChanged(SensorEvent event) {
// Samples may come in around 4 times per second
final float[] smoothed = smoothXYZ(event.values);
final int oldFlipState = mFlipState;
final float totalGravitySquared =
smoothed[0] * smoothed[0] + smoothed[1] * smoothed[1] + smoothed[2] * smoothed[2];
final float minimumGravitySquared = MINIMUM_GRAVITY_FOR_FLIP * MINIMUM_GRAVITY_FOR_FLIP;
final float maximumGravitySquared = MAXIMUM_GRAVITY_FOR_FLIP * MAXIMUM_GRAVITY_FOR_FLIP;
mFlipState = FLIP_STATE_NONE;
if (smoothed[2] > MINIMUM_GRAVITY_FOR_FLIP && smoothed[2] < MAXIMUM_GRAVITY_FOR_FLIP) {
mFlipState = FLIP_STATE_UP;
}
if (smoothed[2] < -MINIMUM_GRAVITY_FOR_FLIP && smoothed[2] > -MAXIMUM_GRAVITY_FOR_FLIP) {
mFlipState = FLIP_STATE_DOWN;
}
// Might overwrite current state, which is what we want.
if (totalGravitySquared < minimumGravitySquared ||
totalGravitySquared > maximumGravitySquared) {
mFlipState = FLIP_STATE_NONE;
}
if (oldFlipState != mFlipState) {
mLastFlipTime = event.timestamp;
}
// We need at least 1/4 seconds to recognize an UP or DOWN state
// We need at least 1 seconds to recognize a NONE state
final long flipDurationNanos = event.timestamp - mLastFlipTime;
switch (mFlipState) {
case FLIP_STATE_DOWN:
if (flipDurationNanos > MINIMUM_UP_DOWN_DURATION && mTriggerState == TRIGGER_STATE_NONE) {
MPLog.v(LOGTAG, "Flip gesture begun");
mTriggerState = TRIGGER_STATE_BEGIN;
}
break;
case FLIP_STATE_UP:
if (flipDurationNanos > MINIMUM_UP_DOWN_DURATION && mTriggerState == TRIGGER_STATE_BEGIN) {
MPLog.v(LOGTAG, "Flip gesture completed");
mTriggerState = TRIGGER_STATE_NONE;
mListener.onFlipGesture();
}
break;
case FLIP_STATE_NONE:
if (flipDurationNanos > MINIMUM_CANCEL_DURATION && mTriggerState != TRIGGER_STATE_NONE) {
MPLog.v(LOGTAG, "Flip gesture abandoned");
mTriggerState = TRIGGER_STATE_NONE;
}
break;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
; // Do nothing
}
private float[] smoothXYZ(final float[] samples) {
// Note that smoothing doesn't depend on sample timestamp!
for (int i = 0; i < 3; i++) {
final float oldVal = mSmoothed[i];
mSmoothed[i] = oldVal + (ACCELEROMETER_SMOOTHING * (samples[i] - oldVal));
}
return mSmoothed;
}
private int mTriggerState = -1;
private int mFlipState = FLIP_STATE_NONE;
private long mLastFlipTime = -1;
private final float[] mSmoothed = new float[3];
private final OnFlipGestureListener mListener;
private static final float MINIMUM_GRAVITY_FOR_FLIP = 9.8f - 2.0f;
private static final float MAXIMUM_GRAVITY_FOR_FLIP = 9.8f + 2.0f;
// 1000000000 one second
// 250000000 one quarter second
private static final long MINIMUM_UP_DOWN_DURATION = 250000000;
private static final long MINIMUM_CANCEL_DURATION = 1000000000;
private static final int FLIP_STATE_UP = -1;
private static final int FLIP_STATE_NONE = 0;
private static final int FLIP_STATE_DOWN = 1;
private static final int TRIGGER_STATE_NONE = 0;
private static final int TRIGGER_STATE_BEGIN = 1;
// Higher is noisier but more responsive, 1.0 to 0.0
private static final float ACCELEROMETER_SMOOTHING = 0.7f;
private static final String LOGTAG = "MixpanelAPI.FlipGesture";
}