Add base class for new falsing manager and classifiers.
This adds no functional changes. It merely adds the framework for a new FalsingManager. Change-Id: I7f0e3b1363c847fa1eefa54bf7793508fefd1926 Test: manual. Bug: 111394067
This commit is contained in:
@@ -16,9 +16,13 @@
|
||||
|
||||
package com.android.systemui.classifier;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* An abstract class for classifiers for touch and sensor events.
|
||||
*/
|
||||
@@ -34,6 +38,21 @@ public abstract class Classifier {
|
||||
public static final int BOUNCER_UNLOCK = 8;
|
||||
public static final int PULSE_EXPAND = 9;
|
||||
|
||||
@IntDef({
|
||||
QUICK_SETTINGS,
|
||||
NOTIFICATION_DISMISS,
|
||||
NOTIFICATION_DRAG_DOWN,
|
||||
NOTIFICATION_DOUBLE_TAP,
|
||||
UNLOCK,
|
||||
LEFT_AFFORDANCE,
|
||||
RIGHT_AFFORDANCE,
|
||||
GENERIC,
|
||||
BOUNCER_UNLOCK,
|
||||
PULSE_EXPAND
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface InteractionType {}
|
||||
|
||||
/**
|
||||
* Contains all the information about touch events from which the classifier can query
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.classifier.brightline;
|
||||
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.systemui.classifier.Classifier;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* FalsingManager designed to make clear why a touch was rejected.
|
||||
*/
|
||||
public class BrightLineFalsingManager implements FalsingManager {
|
||||
|
||||
static final boolean DEBUG = false;
|
||||
private static final String TAG = "FalsingManagerPlugin";
|
||||
|
||||
private final SensorManager mSensorManager;
|
||||
private final FalsingDataProvider mDataProvider;
|
||||
private boolean mSessionStarted;
|
||||
|
||||
private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final List<FalsingClassifier> mClassifiers;
|
||||
|
||||
private SensorEventListener mSensorEventListener = new SensorEventListener() {
|
||||
@Override
|
||||
public synchronized void onSensorChanged(SensorEvent event) {
|
||||
onSensorEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
}
|
||||
};
|
||||
|
||||
BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, SensorManager sensorManager) {
|
||||
mDataProvider = falsingDataProvider;
|
||||
mSensorManager = sensorManager;
|
||||
mClassifiers = new ArrayList<>();
|
||||
// TODO: add classifiers here.
|
||||
}
|
||||
|
||||
private void registerSensors() {
|
||||
Sensor s = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
|
||||
if (s != null) {
|
||||
// This can be expensive, and doesn't need to happen on the main thread.
|
||||
mBackgroundExecutor.submit(() -> {
|
||||
logDebug("registering sensor listener");
|
||||
mSensorManager.registerListener(
|
||||
mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void unregisterSensors() {
|
||||
// This can be expensive, and doesn't need to happen on the main thread.
|
||||
mBackgroundExecutor.submit(() -> {
|
||||
logDebug("unregistering sensor listener");
|
||||
mSensorManager.unregisterListener(mSensorEventListener);
|
||||
});
|
||||
}
|
||||
|
||||
private void sessionStart() {
|
||||
logDebug("Starting Session");
|
||||
mSessionStarted = true;
|
||||
registerSensors();
|
||||
mClassifiers.forEach(FalsingClassifier::onSessionStarted);
|
||||
}
|
||||
|
||||
private void sessionEnd() {
|
||||
if (mSessionStarted) {
|
||||
logDebug("Ending Session");
|
||||
mSessionStarted = false;
|
||||
unregisterSensors();
|
||||
mDataProvider.onSessionEnd();
|
||||
mClassifiers.forEach(FalsingClassifier::onSessionEnded);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInteractionType(@Classifier.InteractionType int type) {
|
||||
logDebug("InteractionType: " + type);
|
||||
mClassifiers.forEach((classifier) -> classifier.setInteractionType(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassiferEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFalseTouch() {
|
||||
boolean r = mClassifiers.stream().anyMatch(falsingClassifier -> {
|
||||
boolean result = falsingClassifier.isFalseTouch();
|
||||
if (result) {
|
||||
logInfo(falsingClassifier.getClass().getName() + ": true");
|
||||
} else {
|
||||
logDebug(falsingClassifier.getClass().getName() + ": false");
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
logDebug("Is false touch? " + r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(MotionEvent motionEvent, int width, int height) {
|
||||
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
|
||||
// make these calls.
|
||||
mDataProvider.onMotionEvent(motionEvent);
|
||||
mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent));
|
||||
}
|
||||
|
||||
private void onSensorEvent(SensorEvent sensorEvent) {
|
||||
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
|
||||
// make these calls.
|
||||
mClassifiers.forEach((classifier) -> classifier.onSensorEvent(sensorEvent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucccessfulUnlock() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationActive() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowingAod(boolean showingAod) {
|
||||
if (showingAod) {
|
||||
sessionEnd();
|
||||
} else {
|
||||
sessionStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificatonStartDraggingDown() {
|
||||
updateInteractionType(Classifier.NOTIFICATION_DRAG_DOWN);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnlockingDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onNotificatonStopDraggingDown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotificationExpanded() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQsDown() {
|
||||
updateInteractionType(Classifier.QUICK_SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQsExpanded(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnforceBouncer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackingStarted(boolean secure) {
|
||||
updateInteractionType(secure ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackingStopped() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeftAffordanceOn() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraOn() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAffordanceSwipingStarted(boolean rightCorner) {
|
||||
updateInteractionType(
|
||||
rightCorner ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAffordanceSwipingAborted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartExpandingFromPulse() {
|
||||
updateInteractionType(Classifier.PULSE_EXPAND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpansionFromPulseStopped() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri reportRejectedTouch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenOnFromTouch() {
|
||||
sessionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReportingEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnlockHintStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraHintStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeftAffordanceHintStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenTurningOn() {
|
||||
sessionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenOff() {
|
||||
sessionEnd();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onNotificatonStopDismissing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationDismissed() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificatonStartDismissing() {
|
||||
updateInteractionType(Classifier.NOTIFICATION_DISMISS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationDoubleTap(boolean b, float v, float v1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBouncerShown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBouncerHidden() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(PrintWriter printWriter) {
|
||||
}
|
||||
|
||||
static void logDebug(String msg) {
|
||||
logDebug(msg, null);
|
||||
}
|
||||
|
||||
static void logDebug(String msg, Throwable throwable) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, msg, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
static void logInfo(String msg) {
|
||||
Log.i(TAG, msg);
|
||||
}
|
||||
|
||||
static void logError(String msg) {
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.classifier.brightline;
|
||||
|
||||
import android.hardware.SensorEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.systemui.classifier.Classifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for rules that determine False touches.
|
||||
*/
|
||||
abstract class FalsingClassifier {
|
||||
private final FalsingDataProvider mDataProvider;
|
||||
|
||||
FalsingClassifier(FalsingDataProvider dataProvider) {
|
||||
this.mDataProvider = dataProvider;
|
||||
}
|
||||
|
||||
List<MotionEvent> getRecentMotionEvents() {
|
||||
return mDataProvider.getRecentMotionEvents();
|
||||
}
|
||||
|
||||
MotionEvent getFirstMotionEvent() {
|
||||
return mDataProvider.getFirstRecentMotionEvent();
|
||||
}
|
||||
|
||||
MotionEvent getLastMotionEvent() {
|
||||
return mDataProvider.getLastMotionEvent();
|
||||
}
|
||||
|
||||
boolean isHorizontal() {
|
||||
return mDataProvider.isHorizontal();
|
||||
}
|
||||
|
||||
boolean isRight() {
|
||||
return mDataProvider.isRight();
|
||||
}
|
||||
|
||||
boolean isVertical() {
|
||||
return mDataProvider.isVertical();
|
||||
}
|
||||
|
||||
boolean isUp() {
|
||||
return mDataProvider.isUp();
|
||||
}
|
||||
|
||||
float getAngle() {
|
||||
return mDataProvider.getAngle();
|
||||
}
|
||||
|
||||
int getWidthPixels() {
|
||||
return mDataProvider.mWidthPixels;
|
||||
}
|
||||
|
||||
int getHeightPixels() {
|
||||
return mDataProvider.mHeightPixels;
|
||||
}
|
||||
|
||||
float getXdpi() {
|
||||
return mDataProvider.mXdpi;
|
||||
}
|
||||
|
||||
float getYdpi() {
|
||||
return mDataProvider.mYdpi;
|
||||
}
|
||||
|
||||
final @Classifier.InteractionType int getInteractionType() {
|
||||
return mDataProvider.getInteractionType();
|
||||
}
|
||||
|
||||
final void setInteractionType(@Classifier.InteractionType int interactionType) {
|
||||
mDataProvider.setInteractionType(interactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a MotionEvent occurs.
|
||||
*
|
||||
* Useful for classifiers that need to see every MotionEvent, but most can probably
|
||||
* use {@link #getRecentMotionEvents()} instead, which will return a list of MotionEvents.
|
||||
*/
|
||||
void onTouchEvent(MotionEvent motionEvent) {};
|
||||
|
||||
/**
|
||||
* Called whenever a SensorEvent occurs, specifically the ProximitySensor.
|
||||
*/
|
||||
void onSensorEvent(SensorEvent sensorEvent) {};
|
||||
|
||||
/**
|
||||
* The phone screen has turned on and we need to begin falsing detection.
|
||||
*/
|
||||
void onSessionStarted() {};
|
||||
|
||||
/**
|
||||
* The phone screen has turned off and falsing data can be discarded.
|
||||
*/
|
||||
void onSessionEnded() {};
|
||||
|
||||
/**
|
||||
* Returns true if the data captured so far looks like a false touch.
|
||||
*/
|
||||
abstract boolean isFalseTouch();
|
||||
|
||||
static void logDebug(String msg) {
|
||||
BrightLineFalsingManager.logDebug(msg);
|
||||
}
|
||||
|
||||
static void logInfo(String msg) {
|
||||
BrightLineFalsingManager.logInfo(msg);
|
||||
}
|
||||
|
||||
static void logError(String msg) {
|
||||
BrightLineFalsingManager.logError(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.classifier.brightline;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
import android.view.MotionEvent.PointerProperties;
|
||||
|
||||
import com.android.systemui.classifier.Classifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Acts as a cache and utility class for FalsingClassifiers.
|
||||
*/
|
||||
class FalsingDataProvider {
|
||||
|
||||
private static final long MOTION_EVENT_AGE_MS = 1000;
|
||||
final int mWidthPixels;
|
||||
final int mHeightPixels;
|
||||
final float mXdpi;
|
||||
final float mYdpi;
|
||||
|
||||
private @Classifier.InteractionType int mInteractionType;
|
||||
private final TimeLimitedMotionEventBuffer mRecentMotionEvents =
|
||||
new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
|
||||
|
||||
private boolean mDirty = true;
|
||||
|
||||
private float mAngle = 0;
|
||||
private MotionEvent mFirstActualMotionEvent;
|
||||
private MotionEvent mFirstRecentMotionEvent;
|
||||
private MotionEvent mLastMotionEvent;
|
||||
|
||||
FalsingDataProvider(Context context) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
mXdpi = displayMetrics.xdpi;
|
||||
mYdpi = displayMetrics.ydpi;
|
||||
mWidthPixels = displayMetrics.widthPixels;
|
||||
mHeightPixels = displayMetrics.heightPixels;
|
||||
|
||||
FalsingClassifier.logInfo("xdpi, ydpi: " + mXdpi + ", " + mYdpi);
|
||||
FalsingClassifier.logInfo("width, height: " + mWidthPixels + ", " + mHeightPixels);
|
||||
}
|
||||
|
||||
void onMotionEvent(MotionEvent motionEvent) {
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mFirstActualMotionEvent = motionEvent;
|
||||
}
|
||||
|
||||
List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
|
||||
FalsingClassifier.logDebug("Unpacked into: " + motionEvents.size());
|
||||
if (BrightLineFalsingManager.DEBUG) {
|
||||
for (MotionEvent m : motionEvents) {
|
||||
FalsingClassifier.logDebug(
|
||||
"x,y,t: " + m.getX() + "," + m.getY() + "," + m.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mRecentMotionEvents.clear();
|
||||
}
|
||||
mRecentMotionEvents.addAll(motionEvents);
|
||||
|
||||
FalsingClassifier.logDebug("Size: " + mRecentMotionEvents.size());
|
||||
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
List<MotionEvent> getRecentMotionEvents() {
|
||||
return mRecentMotionEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
|
||||
*/
|
||||
final void setInteractionType(@Classifier.InteractionType int interactionType) {
|
||||
this.mInteractionType = interactionType;
|
||||
}
|
||||
|
||||
final int getInteractionType() {
|
||||
return mInteractionType;
|
||||
}
|
||||
|
||||
MotionEvent getFirstActualMotionEvent() {
|
||||
return mFirstActualMotionEvent;
|
||||
}
|
||||
|
||||
MotionEvent getFirstRecentMotionEvent() {
|
||||
recalculateData();
|
||||
return mFirstRecentMotionEvent;
|
||||
}
|
||||
|
||||
MotionEvent getLastMotionEvent() {
|
||||
recalculateData();
|
||||
return mLastMotionEvent;
|
||||
}
|
||||
|
||||
float getAngle() {
|
||||
recalculateData();
|
||||
return mAngle;
|
||||
}
|
||||
|
||||
boolean isHorizontal() {
|
||||
recalculateData();
|
||||
return Math.abs(mFirstRecentMotionEvent.getX() - mLastMotionEvent.getX()) > Math
|
||||
.abs(mFirstRecentMotionEvent.getY() - mLastMotionEvent.getY());
|
||||
}
|
||||
|
||||
boolean isRight() {
|
||||
recalculateData();
|
||||
return mLastMotionEvent.getX() > mFirstRecentMotionEvent.getX();
|
||||
}
|
||||
|
||||
boolean isVertical() {
|
||||
return !isHorizontal();
|
||||
}
|
||||
|
||||
boolean isUp() {
|
||||
recalculateData();
|
||||
return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
|
||||
}
|
||||
|
||||
private void recalculateData() {
|
||||
if (!mDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFirstRecentMotionEvent = mRecentMotionEvents.get(0);
|
||||
mLastMotionEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);
|
||||
|
||||
calculateAngleInternal();
|
||||
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
private void calculateAngleInternal() {
|
||||
if (mRecentMotionEvents.size() < 2) {
|
||||
mAngle = Float.MAX_VALUE;
|
||||
} else {
|
||||
float lastX = mLastMotionEvent.getX() - mFirstRecentMotionEvent.getX();
|
||||
float lastY = mLastMotionEvent.getY() - mFirstRecentMotionEvent.getY();
|
||||
|
||||
mAngle = (float) Math.atan2(lastY, lastX);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
|
||||
List<MotionEvent> motionEvents = new ArrayList<>();
|
||||
List<PointerProperties> pointerPropertiesList = new ArrayList<>();
|
||||
int pointerCount = motionEvent.getPointerCount();
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
PointerProperties pointerProperties = new PointerProperties();
|
||||
motionEvent.getPointerProperties(i, pointerProperties);
|
||||
pointerPropertiesList.add(pointerProperties);
|
||||
}
|
||||
PointerProperties[] pointerPropertiesArray = new PointerProperties[pointerPropertiesList
|
||||
.size()];
|
||||
pointerPropertiesList.toArray(pointerPropertiesArray);
|
||||
|
||||
int historySize = motionEvent.getHistorySize();
|
||||
for (int i = 0; i < historySize; i++) {
|
||||
List<PointerCoords> pointerCoordsList = new ArrayList<>();
|
||||
for (int j = 0; j < pointerCount; j++) {
|
||||
PointerCoords pointerCoords = new PointerCoords();
|
||||
motionEvent.getHistoricalPointerCoords(j, i, pointerCoords);
|
||||
pointerCoordsList.add(pointerCoords);
|
||||
}
|
||||
motionEvents.add(MotionEvent.obtain(
|
||||
motionEvent.getDownTime(),
|
||||
motionEvent.getHistoricalEventTime(i),
|
||||
motionEvent.getAction(),
|
||||
pointerCount,
|
||||
pointerPropertiesArray,
|
||||
pointerCoordsList.toArray(new PointerCoords[0]),
|
||||
motionEvent.getMetaState(),
|
||||
motionEvent.getButtonState(),
|
||||
motionEvent.getXPrecision(),
|
||||
motionEvent.getYPrecision(),
|
||||
motionEvent.getDeviceId(),
|
||||
motionEvent.getEdgeFlags(),
|
||||
motionEvent.getSource(),
|
||||
motionEvent.getFlags()
|
||||
));
|
||||
}
|
||||
|
||||
motionEvents.add(MotionEvent.obtainNoHistory(motionEvent));
|
||||
|
||||
return motionEvents;
|
||||
}
|
||||
|
||||
void onSessionEnd() {
|
||||
mFirstActualMotionEvent = null;
|
||||
|
||||
for (MotionEvent ev : mRecentMotionEvents) {
|
||||
ev.recycle();
|
||||
}
|
||||
|
||||
mRecentMotionEvents.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.classifier.brightline;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Maintains an ordered list of the last N milliseconds of MotionEvents.
|
||||
*
|
||||
* This class is simply a convenience class designed to look like a simple list, but that
|
||||
* automatically discards old MotionEvents. It functions much like a queue - first in first out -
|
||||
* but does not have a fixed size like a circular buffer.
|
||||
*/
|
||||
public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
|
||||
|
||||
private final LinkedList<MotionEvent> mMotionEvents;
|
||||
private long mMaxAgeMs;
|
||||
|
||||
TimeLimitedMotionEventBuffer(long maxAgeMs) {
|
||||
super();
|
||||
this.mMaxAgeMs = maxAgeMs;
|
||||
this.mMotionEvents = new LinkedList<>();
|
||||
}
|
||||
|
||||
private void ejectOldEvents() {
|
||||
if (mMotionEvents.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Iterator<MotionEvent> iter = listIterator();
|
||||
long mostRecentMs = mMotionEvents.getLast().getEventTime();
|
||||
while (iter.hasNext()) {
|
||||
MotionEvent ev = iter.next();
|
||||
if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
|
||||
iter.remove();
|
||||
ev.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, MotionEvent element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MotionEvent remove(int index) {
|
||||
return mMotionEvents.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return mMotionEvents.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return mMotionEvents.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mMotionEvents.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return mMotionEvents.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return mMotionEvents.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<MotionEvent> iterator() {
|
||||
return mMotionEvents.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return mMotionEvents.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return mMotionEvents.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(MotionEvent element) {
|
||||
boolean result = mMotionEvents.add(element);
|
||||
ejectOldEvents();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return mMotionEvents.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return mMotionEvents.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends MotionEvent> collection) {
|
||||
boolean result = mMotionEvents.addAll(collection);
|
||||
ejectOldEvents();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends MotionEvent> elements) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return mMotionEvents.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return mMotionEvents.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
mMotionEvents.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return mMotionEvents.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mMotionEvents.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MotionEvent get(int index) {
|
||||
return mMotionEvents.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MotionEvent set(int index, MotionEvent element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<MotionEvent> listIterator() {
|
||||
return new Iter(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<MotionEvent> listIterator(int index) {
|
||||
return new Iter(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MotionEvent> subList(int fromIndex, int toIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
class Iter implements ListIterator<MotionEvent> {
|
||||
|
||||
private final ListIterator<MotionEvent> mIterator;
|
||||
|
||||
Iter(int index) {
|
||||
this.mIterator = mMotionEvents.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MotionEvent next() {
|
||||
return mIterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return mIterator.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MotionEvent previous() {
|
||||
return mIterator.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return mIterator.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return mIterator.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
mIterator.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(MotionEvent motionEvent) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(MotionEvent element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.classifier.brightline;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class FalsingDataProviderTest extends SysuiTestCase {
|
||||
|
||||
private FalsingDataProvider mDataProvider;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mDataProvider = new FalsingDataProvider(getContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_trackMotionEvents() {
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 2, 9);
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 4, 7);
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_UP, 3, 6, 5);
|
||||
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
|
||||
|
||||
assertThat(motionEventList.size(), is(3));
|
||||
assertThat(motionEventList.get(0).getActionMasked(), is(MotionEvent.ACTION_DOWN));
|
||||
assertThat(motionEventList.get(1).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(2).getActionMasked(), is(MotionEvent.ACTION_UP));
|
||||
assertThat(motionEventList.get(0).getEventTime(), is(1L));
|
||||
assertThat(motionEventList.get(1).getEventTime(), is(2L));
|
||||
assertThat(motionEventList.get(2).getEventTime(), is(3L));
|
||||
assertThat(motionEventList.get(0).getX(), is(2f));
|
||||
assertThat(motionEventList.get(1).getX(), is(4f));
|
||||
assertThat(motionEventList.get(2).getX(), is(6f));
|
||||
assertThat(motionEventList.get(0).getY(), is(9f));
|
||||
assertThat(motionEventList.get(1).getY(), is(7f));
|
||||
assertThat(motionEventList.get(2).getY(), is(5f));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_trackRecentMotionEvents() {
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 2, 9);
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 800, 4, 7);
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_UP, 1200, 6, 5);
|
||||
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
|
||||
|
||||
assertThat(motionEventList.size(), is(2));
|
||||
assertThat(motionEventList.get(0).getActionMasked(), is(MotionEvent.ACTION_DOWN));
|
||||
assertThat(motionEventList.get(1).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(0).getEventTime(), is(1L));
|
||||
assertThat(motionEventList.get(1).getEventTime(), is(800L));
|
||||
assertThat(motionEventList.get(0).getX(), is(2f));
|
||||
assertThat(motionEventList.get(1).getX(), is(4f));
|
||||
assertThat(motionEventList.get(0).getY(), is(9f));
|
||||
assertThat(motionEventList.get(1).getY(), is(7f));
|
||||
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
|
||||
// Still two events, but event a is gone.
|
||||
assertThat(motionEventList.size(), is(2));
|
||||
assertThat(motionEventList.get(0).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(1).getActionMasked(), is(MotionEvent.ACTION_UP));
|
||||
assertThat(motionEventList.get(0).getEventTime(), is(800L));
|
||||
assertThat(motionEventList.get(1).getEventTime(), is(1200L));
|
||||
assertThat(motionEventList.get(0).getX(), is(4f));
|
||||
assertThat(motionEventList.get(1).getX(), is(6f));
|
||||
assertThat(motionEventList.get(0).getY(), is(7f));
|
||||
assertThat(motionEventList.get(1).getY(), is(5f));
|
||||
|
||||
// The first, real event should still be a, however.
|
||||
MotionEvent firstRealMotionEvent = mDataProvider.getFirstActualMotionEvent();
|
||||
assertThat(firstRealMotionEvent.getActionMasked(), is(MotionEvent.ACTION_DOWN));
|
||||
assertThat(firstRealMotionEvent.getEventTime(), is(1L));
|
||||
assertThat(firstRealMotionEvent.getX(), is(2f));
|
||||
assertThat(firstRealMotionEvent.getY(), is(9f));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_unpackMotionEvents() {
|
||||
// Batching only works for motion events of the same type.
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 1, 2, 9);
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 4, 7);
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 3, 6, 5);
|
||||
motionEventA.addBatch(motionEventB);
|
||||
motionEventA.addBatch(motionEventC);
|
||||
// Note that calling addBatch changes properties on the original event, not just it's
|
||||
// historical artifacts.
|
||||
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
|
||||
|
||||
assertThat(motionEventList.size(), is(3));
|
||||
assertThat(motionEventList.get(0).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(1).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(2).getActionMasked(), is(MotionEvent.ACTION_MOVE));
|
||||
assertThat(motionEventList.get(0).getEventTime(), is(1L));
|
||||
assertThat(motionEventList.get(1).getEventTime(), is(2L));
|
||||
assertThat(motionEventList.get(2).getEventTime(), is(3L));
|
||||
assertThat(motionEventList.get(0).getX(), is(2f));
|
||||
assertThat(motionEventList.get(1).getX(), is(4f));
|
||||
assertThat(motionEventList.get(2).getX(), is(6f));
|
||||
assertThat(motionEventList.get(0).getY(), is(9f));
|
||||
assertThat(motionEventList.get(1).getY(), is(7f));
|
||||
assertThat(motionEventList.get(2).getY(), is(5f));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAngle() {
|
||||
MotionEvent motionEventOrigin = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 0, 0);
|
||||
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 1, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
assertThat((double) mDataProvider.getAngle(), closeTo(Math.PI / 4, .001));
|
||||
motionEventA.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -1, -1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
assertThat((double) mDataProvider.getAngle(), closeTo(-3 * Math.PI / 4, .001));
|
||||
motionEventB.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 2, 0);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
assertThat((double) mDataProvider.getAngle(), closeTo(0, .001));
|
||||
motionEventC.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_isHorizontal() {
|
||||
MotionEvent motionEventOrigin = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 0, 0);
|
||||
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 1, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
assertThat(mDataProvider.isHorizontal(), is(false));
|
||||
motionEventA.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 2, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
assertThat(mDataProvider.isHorizontal(), is(true));
|
||||
motionEventB.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -3, -1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
assertThat(mDataProvider.isHorizontal(), is(true));
|
||||
motionEventC.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_isVertical() {
|
||||
MotionEvent motionEventOrigin = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 0, 0);
|
||||
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 1, 0);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
assertThat(mDataProvider.isVertical(), is(false));
|
||||
motionEventA.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 0, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
assertThat(mDataProvider.isVertical(), is(true));
|
||||
motionEventB.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -3, -10);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
assertThat(mDataProvider.isVertical(), is(true));
|
||||
motionEventC.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_isRight() {
|
||||
MotionEvent motionEventOrigin = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 0, 0);
|
||||
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 1, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
assertThat(mDataProvider.isRight(), is(true));
|
||||
motionEventA.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 0, 1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
assertThat(mDataProvider.isRight(), is(false));
|
||||
motionEventB.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -3, -10);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
assertThat(mDataProvider.isRight(), is(false));
|
||||
motionEventC.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_isUp() {
|
||||
// Remember that our y axis is flipped.
|
||||
|
||||
MotionEvent motionEventOrigin = obtainMotionEvent(MotionEvent.ACTION_DOWN, 1, 0, 0);
|
||||
|
||||
MotionEvent motionEventA = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 1, -1);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventA);
|
||||
assertThat(mDataProvider.isUp(), is(true));
|
||||
motionEventA.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, 0, 0);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventB);
|
||||
assertThat(mDataProvider.isUp(), is(false));
|
||||
motionEventB.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
|
||||
MotionEvent motionEventC = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -3, 10);
|
||||
mDataProvider.onMotionEvent(motionEventOrigin);
|
||||
mDataProvider.onMotionEvent(motionEventC);
|
||||
assertThat(mDataProvider.isUp(), is(false));
|
||||
motionEventC.recycle();
|
||||
mDataProvider.onSessionEnd();
|
||||
}
|
||||
|
||||
private MotionEvent obtainMotionEvent(int action, long eventTimeMs, float x, float y) {
|
||||
return MotionEvent.obtain(1, eventTimeMs, action, x, y, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user