Merge changes from topic "b111394067-new-falsing-manager" into qt-dev
am: acd240fbb3
Change-Id: I83640ae5c6a6681eb33c87a70a629573f189d024
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,328 @@
|
||||
/*
|
||||
* 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<>();
|
||||
DistanceClassifier distanceClassifier = new DistanceClassifier(mDataProvider);
|
||||
ProximityClassifier proximityClassifier = new ProximityClassifier(distanceClassifier,
|
||||
mDataProvider);
|
||||
mClassifiers.add(new PointerCountClassifier(mDataProvider));
|
||||
mClassifiers.add(new TypeClassifier(mDataProvider));
|
||||
mClassifiers.add(new DiagonalClassifier(mDataProvider));
|
||||
mClassifiers.add(distanceClassifier);
|
||||
mClassifiers.add(proximityClassifier);
|
||||
mClassifiers.add(new ZigZagClassifier(mDataProvider));
|
||||
}
|
||||
|
||||
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,89 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
|
||||
|
||||
/**
|
||||
* False on swipes that are too close to 45 degrees.
|
||||
*
|
||||
* Horizontal swipes may have a different threshold than vertical.
|
||||
*
|
||||
* This falser should not run on "affordance" swipes, as they will always be close to 45.
|
||||
*/
|
||||
class DiagonalClassifier extends FalsingClassifier {
|
||||
|
||||
private static final float HORIZONTAL_ANGLE_RANGE = (float) (5f / 360f * Math.PI * 2f);
|
||||
private static final float VERTICAL_ANGLE_RANGE = (float) (5f / 360f * Math.PI * 2f);
|
||||
private static final float DIAGONAL = (float) (Math.PI / 4); // 45 deg
|
||||
private static final float NINETY_DEG = (float) (Math.PI / 2);
|
||||
private static final float ONE_HUNDRED_EIGHTY_DEG = (float) (Math.PI);
|
||||
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
|
||||
|
||||
DiagonalClassifier(FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFalseTouch() {
|
||||
float angle = getAngle();
|
||||
|
||||
if (angle == Float.MAX_VALUE) { // Unknown angle
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getInteractionType() == LEFT_AFFORDANCE
|
||||
|| getInteractionType() == RIGHT_AFFORDANCE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float minAngle = DIAGONAL - HORIZONTAL_ANGLE_RANGE;
|
||||
float maxAngle = DIAGONAL + HORIZONTAL_ANGLE_RANGE;
|
||||
if (isVertical()) {
|
||||
minAngle = DIAGONAL - VERTICAL_ANGLE_RANGE;
|
||||
maxAngle = DIAGONAL + VERTICAL_ANGLE_RANGE;
|
||||
}
|
||||
|
||||
return angleBetween(angle, minAngle, maxAngle)
|
||||
|| angleBetween(angle, minAngle + NINETY_DEG, maxAngle + NINETY_DEG)
|
||||
|| angleBetween(angle, minAngle - NINETY_DEG, maxAngle - NINETY_DEG)
|
||||
|| angleBetween(angle, minAngle + ONE_HUNDRED_EIGHTY_DEG,
|
||||
maxAngle + ONE_HUNDRED_EIGHTY_DEG);
|
||||
}
|
||||
|
||||
private boolean angleBetween(float angle, float min, float max) {
|
||||
// No need to normalize angle as it is guaranteed to be between 0 and 2*PI.
|
||||
min = normalizeAngle(min);
|
||||
max = normalizeAngle(max);
|
||||
|
||||
if (min > max) { // Can happen when angle is close to 0.
|
||||
return angle >= min || angle <= max;
|
||||
}
|
||||
|
||||
return angle >= min && angle <= max;
|
||||
}
|
||||
|
||||
private float normalizeAngle(float angle) {
|
||||
if (angle < 0) {
|
||||
return THREE_HUNDRED_SIXTY_DEG + (angle % THREE_HUNDRED_SIXTY_DEG);
|
||||
} else if (angle > THREE_HUNDRED_SIXTY_DEG) {
|
||||
return angle % THREE_HUNDRED_SIXTY_DEG;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 android.view.VelocityTracker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ensure that the swipe + momentum covers a minimum distance.
|
||||
*/
|
||||
class DistanceClassifier extends FalsingClassifier {
|
||||
|
||||
private static final float HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN = 1;
|
||||
private static final float VERTICAL_FLING_THRESHOLD_DISTANCE_IN = 1;
|
||||
private static final float HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN = 3;
|
||||
private static final float VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN = 3;
|
||||
private static final float VELOCITY_TO_DISTANCE = 80f;
|
||||
private static final float SCREEN_FRACTION_MIN_DISTANCE = 0.8f;
|
||||
|
||||
private final float mVerticalFlingThresholdPx;
|
||||
private final float mHorizontalFlingThresholdPx;
|
||||
private final float mVerticalSwipeThresholdPx;
|
||||
private final float mHorizontalSwipeThresholdPx;
|
||||
|
||||
private boolean mDistanceDirty;
|
||||
private DistanceVectors mCachedDistance;
|
||||
|
||||
DistanceClassifier(FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
|
||||
mHorizontalFlingThresholdPx = Math
|
||||
.min(getWidthPixels() * SCREEN_FRACTION_MIN_DISTANCE,
|
||||
HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN * getXdpi());
|
||||
mVerticalFlingThresholdPx = Math
|
||||
.min(getHeightPixels() * SCREEN_FRACTION_MIN_DISTANCE,
|
||||
VERTICAL_FLING_THRESHOLD_DISTANCE_IN * getYdpi());
|
||||
mHorizontalSwipeThresholdPx = Math
|
||||
.min(getWidthPixels() * SCREEN_FRACTION_MIN_DISTANCE,
|
||||
HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN * getXdpi());
|
||||
mVerticalSwipeThresholdPx = Math
|
||||
.min(getHeightPixels() * SCREEN_FRACTION_MIN_DISTANCE,
|
||||
VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN * getYdpi());
|
||||
mDistanceDirty = true;
|
||||
}
|
||||
|
||||
private DistanceVectors getDistances() {
|
||||
if (mDistanceDirty) {
|
||||
mCachedDistance = calculateDistances();
|
||||
mDistanceDirty = false;
|
||||
}
|
||||
|
||||
return mCachedDistance;
|
||||
}
|
||||
|
||||
private DistanceVectors calculateDistances() {
|
||||
// This code assumes that there will be no missed DOWN or UP events.
|
||||
VelocityTracker velocityTracker = VelocityTracker.obtain();
|
||||
List<MotionEvent> motionEvents = getRecentMotionEvents();
|
||||
|
||||
if (motionEvents.size() < 3) {
|
||||
logDebug("Only " + motionEvents.size() + " motion events recorded.");
|
||||
return new DistanceVectors(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
for (MotionEvent motionEvent : motionEvents) {
|
||||
velocityTracker.addMovement(motionEvent);
|
||||
}
|
||||
velocityTracker.computeCurrentVelocity(1);
|
||||
|
||||
float vX = velocityTracker.getXVelocity();
|
||||
float vY = velocityTracker.getYVelocity();
|
||||
|
||||
velocityTracker.recycle();
|
||||
|
||||
float dX = getLastMotionEvent().getX() - getFirstMotionEvent().getX();
|
||||
float dY = getLastMotionEvent().getY() - getFirstMotionEvent().getY();
|
||||
|
||||
logInfo("dX: " + dX + " dY: " + dY + " xV: " + vX + " yV: " + vY);
|
||||
|
||||
return new DistanceVectors(dX, dY, vX, vY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(MotionEvent motionEvent) {
|
||||
mDistanceDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFalseTouch() {
|
||||
return !getDistances().getPassedFlingThreshold();
|
||||
}
|
||||
|
||||
boolean isLongSwipe() {
|
||||
boolean longSwipe = getDistances().getPassedDistanceThreshold();
|
||||
logDebug("Is longSwipe? " + longSwipe);
|
||||
return longSwipe;
|
||||
}
|
||||
|
||||
private class DistanceVectors {
|
||||
final float mDx;
|
||||
final float mDy;
|
||||
private final float mVx;
|
||||
private final float mVy;
|
||||
|
||||
DistanceVectors(float dX, float dY, float vX, float vY) {
|
||||
this.mDx = dX;
|
||||
this.mDy = dY;
|
||||
this.mVx = vX;
|
||||
this.mVy = vY;
|
||||
}
|
||||
|
||||
boolean getPassedDistanceThreshold() {
|
||||
if (isHorizontal()) {
|
||||
logDebug("Horizontal swipe distance: " + Math.abs(mDx));
|
||||
logDebug("Threshold: " + mHorizontalSwipeThresholdPx);
|
||||
|
||||
return Math.abs(mDx) >= mHorizontalSwipeThresholdPx;
|
||||
}
|
||||
|
||||
logDebug("Vertical swipe distance: " + Math.abs(mDy));
|
||||
logDebug("Threshold: " + mVerticalSwipeThresholdPx);
|
||||
return Math.abs(mDy) >= mVerticalSwipeThresholdPx;
|
||||
}
|
||||
|
||||
boolean getPassedFlingThreshold() {
|
||||
float dX = this.mDx + this.mVx * VELOCITY_TO_DISTANCE;
|
||||
float dY = this.mDy + this.mVy * VELOCITY_TO_DISTANCE;
|
||||
|
||||
if (isHorizontal()) {
|
||||
logDebug("Horizontal swipe and fling distance: " + this.mDx + ", "
|
||||
+ this.mVx * VELOCITY_TO_DISTANCE);
|
||||
logDebug("Threshold: " + mHorizontalFlingThresholdPx);
|
||||
return Math.abs(dX) >= mHorizontalFlingThresholdPx;
|
||||
}
|
||||
|
||||
logDebug("Vertical swipe and fling distance: " + this.mDy + ", "
|
||||
+ this.mVy * VELOCITY_TO_DISTANCE);
|
||||
logDebug("Threshold: " + mVerticalFlingThresholdPx);
|
||||
return Math.abs(dY) >= mVerticalFlingThresholdPx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.getWidthPixels();
|
||||
}
|
||||
|
||||
int getHeightPixels() {
|
||||
return mDataProvider.getHeightPixels();
|
||||
}
|
||||
|
||||
float getXdpi() {
|
||||
return mDataProvider.getXdpi();
|
||||
}
|
||||
|
||||
float getYdpi() {
|
||||
return mDataProvider.getYdpi();
|
||||
}
|
||||
|
||||
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,249 @@
|
||||
/*
|
||||
* 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;
|
||||
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
|
||||
|
||||
private final int mWidthPixels;
|
||||
private final int mHeightPixels;
|
||||
private final float mXdpi;
|
||||
private 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: " + getXdpi() + ", " + getYdpi());
|
||||
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** Returns screen width in pixels. */
|
||||
int getWidthPixels() {
|
||||
return mWidthPixels;
|
||||
}
|
||||
|
||||
/** Returns screen height in pixels. */
|
||||
int getHeightPixels() {
|
||||
return mHeightPixels;
|
||||
}
|
||||
|
||||
float getXdpi() {
|
||||
return mXdpi;
|
||||
}
|
||||
|
||||
float getYdpi() {
|
||||
return mYdpi;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle between the first and last point of the recent points.
|
||||
*
|
||||
* The angle will be in radians, always be between 0 and 2*PI, inclusive.
|
||||
*/
|
||||
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);
|
||||
while (mAngle < 0) {
|
||||
mAngle += THREE_HUNDRED_SIXTY_DEG;
|
||||
}
|
||||
while (mAngle > THREE_HUNDRED_SIXTY_DEG) {
|
||||
mAngle -= THREE_HUNDRED_SIXTY_DEG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* False touch if more than one finger touches the screen.
|
||||
*
|
||||
* IMPORTANT: This should not be used for certain cases (i.e. a11y) as we expect multiple fingers
|
||||
* for them.
|
||||
*/
|
||||
class PointerCountClassifier extends FalsingClassifier {
|
||||
|
||||
private static final int MAX_ALLOWED_POINTERS = 1;
|
||||
private int mMaxPointerCount;
|
||||
|
||||
PointerCountClassifier(FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(MotionEvent motionEvent) {
|
||||
int pCount = mMaxPointerCount;
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mMaxPointerCount = motionEvent.getPointerCount();
|
||||
} else {
|
||||
mMaxPointerCount = Math.max(mMaxPointerCount, motionEvent.getPointerCount());
|
||||
}
|
||||
if (pCount != mMaxPointerCount) {
|
||||
logDebug("Pointers observed:" + mMaxPointerCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFalseTouch() {
|
||||
return mMaxPointerCount > MAX_ALLOWED_POINTERS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
|
||||
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
|
||||
/**
|
||||
* False touch if proximity sensor is covered for more than a certain percentage of the gesture.
|
||||
*
|
||||
* This classifer is essentially a no-op for QUICK_SETTINGS, as we assume the sensor may be
|
||||
* covered when swiping from the top.
|
||||
*/
|
||||
class ProximityClassifier extends FalsingClassifier {
|
||||
|
||||
private static final double PERCENT_COVERED_THRESHOLD = 0.1;
|
||||
private final DistanceClassifier mDistanceClassifier;
|
||||
|
||||
private boolean mNear;
|
||||
private long mGestureStartTimeNs;
|
||||
private long mPrevNearTimeNs;
|
||||
private long mNearDurationNs;
|
||||
private float mPercentNear;
|
||||
|
||||
ProximityClassifier(DistanceClassifier distanceClassifier,
|
||||
FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
this.mDistanceClassifier = distanceClassifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onSessionStarted() {
|
||||
mPrevNearTimeNs = 0;
|
||||
mPercentNear = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onSessionEnded() {
|
||||
mPrevNearTimeNs = 0;
|
||||
mPercentNear = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(MotionEvent motionEvent) {
|
||||
int action = motionEvent.getActionMasked();
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mGestureStartTimeNs = motionEvent.getEventTimeNano();
|
||||
if (mPrevNearTimeNs > 0) {
|
||||
// We only care about if the proximity sensor is triggered while a move event is
|
||||
// happening.
|
||||
mPrevNearTimeNs = motionEvent.getEventTimeNano();
|
||||
}
|
||||
logDebug("Gesture start time: " + mGestureStartTimeNs);
|
||||
mNearDurationNs = 0;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
update(mNear, motionEvent.getEventTimeNano());
|
||||
long duration = motionEvent.getEventTimeNano() - mGestureStartTimeNs;
|
||||
|
||||
logDebug("Gesture duration, Proximity duration: " + duration + ", " + mNearDurationNs);
|
||||
|
||||
if (duration == 0) {
|
||||
mPercentNear = mNear ? 1.0f : 0.0f;
|
||||
} else {
|
||||
mPercentNear = (float) mNearDurationNs / (float) duration;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorEvent(SensorEvent sensorEvent) {
|
||||
if (sensorEvent.sensor.getType() == Sensor.TYPE_PROXIMITY) {
|
||||
logDebug("Sensor is: " + (sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange())
|
||||
+ " at time " + sensorEvent.timestamp);
|
||||
update(
|
||||
sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange(),
|
||||
sensorEvent.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFalseTouch() {
|
||||
if (getInteractionType() == QUICK_SETTINGS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logInfo("Percent of gesture in proximity: " + mPercentNear);
|
||||
|
||||
if (mPercentNear > PERCENT_COVERED_THRESHOLD) {
|
||||
return !mDistanceClassifier.isLongSwipe();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param near is the sensor showing the near state right now
|
||||
* @param timeStampNs time of this event in nanoseconds
|
||||
*/
|
||||
private void update(boolean near, long timeStampNs) {
|
||||
if (mPrevNearTimeNs != 0 && timeStampNs > mPrevNearTimeNs && mNear) {
|
||||
mNearDurationNs += timeStampNs - mPrevNearTimeNs;
|
||||
logDebug("Updating duration: " + mNearDurationNs);
|
||||
}
|
||||
|
||||
if (near) {
|
||||
logDebug("Set prevNearTimeNs: " + timeStampNs);
|
||||
mPrevNearTimeNs = timeStampNs;
|
||||
}
|
||||
|
||||
mNear = near;
|
||||
}
|
||||
}
|
||||
@@ -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,61 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
|
||||
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
|
||||
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
|
||||
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
|
||||
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
|
||||
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.UNLOCK;
|
||||
|
||||
/**
|
||||
* Ensure that the swipe direction generally matches that of the interaction type.
|
||||
*/
|
||||
public class TypeClassifier extends FalsingClassifier {
|
||||
TypeClassifier(FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFalseTouch() {
|
||||
boolean vertical = isVertical();
|
||||
boolean up = isUp();
|
||||
boolean right = isRight();
|
||||
|
||||
switch (getInteractionType()) {
|
||||
case QUICK_SETTINGS:
|
||||
case PULSE_EXPAND:
|
||||
case NOTIFICATION_DRAG_DOWN:
|
||||
return !vertical || up;
|
||||
case NOTIFICATION_DISMISS:
|
||||
return vertical;
|
||||
case UNLOCK:
|
||||
case BOUNCER_UNLOCK:
|
||||
return !vertical || !up;
|
||||
case LEFT_AFFORDANCE: // Swiping from the bottom left corner for camera or similar.
|
||||
return !right || !up;
|
||||
case RIGHT_AFFORDANCE: // Swiping from the bottom right corner for camera or similar.
|
||||
return right || !up;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.graphics.Point;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Penalizes gestures that change direction in either the x or y too much.
|
||||
*/
|
||||
class ZigZagClassifier extends FalsingClassifier {
|
||||
|
||||
// Define how far one can move back and forth over one inch of travel before being falsed.
|
||||
// `PRIMARY` defines how far one can deviate in the primary direction of travel. I.e. if you're
|
||||
// swiping vertically, you shouldn't have a lot of zig zag in the vertical direction. Since
|
||||
// most swipes will follow somewhat of a 'C' or 'S' shape, we allow more deviance along the
|
||||
// `SECONDARY` axis.
|
||||
private static final float MAX_X_PRIMARY_DEVIANCE = .05f;
|
||||
private static final float MAX_Y_PRIMARY_DEVIANCE = .05f;
|
||||
private static final float MAX_X_SECONDARY_DEVIANCE = .3f;
|
||||
private static final float MAX_Y_SECONDARY_DEVIANCE = .3f;
|
||||
|
||||
ZigZagClassifier(FalsingDataProvider dataProvider) {
|
||||
super(dataProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFalseTouch() {
|
||||
List<MotionEvent> motionEvents = getRecentMotionEvents();
|
||||
// Rotate horizontal gestures to be horizontal between their first and last point.
|
||||
// Rotate vertical gestures to be vertical between their first and last point.
|
||||
// Sum the absolute value of every dx and dy along the gesture. Compare this with the dx
|
||||
// and dy
|
||||
// between the first and last point.
|
||||
// For horizontal lines, the difference in the x direction should be small.
|
||||
// For vertical lines, the difference in the y direction should be small.
|
||||
|
||||
if (motionEvents.size() < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Point> rotatedPoints;
|
||||
if (isHorizontal()) {
|
||||
rotatedPoints = rotateHorizontal();
|
||||
} else {
|
||||
rotatedPoints = rotateVertical();
|
||||
}
|
||||
|
||||
float actualDx = Math
|
||||
.abs(rotatedPoints.get(0).x - rotatedPoints.get(rotatedPoints.size() - 1).x);
|
||||
float actualDy = Math
|
||||
.abs(rotatedPoints.get(0).y - rotatedPoints.get(rotatedPoints.size() - 1).y);
|
||||
logDebug("Actual: (" + actualDx + "," + actualDy + ")");
|
||||
float runningAbsDx = 0;
|
||||
float runningAbsDy = 0;
|
||||
float pX = 0;
|
||||
float pY = 0;
|
||||
boolean firstLoop = true;
|
||||
for (Point point : rotatedPoints) {
|
||||
if (firstLoop) {
|
||||
pX = point.x;
|
||||
pY = point.y;
|
||||
firstLoop = false;
|
||||
continue;
|
||||
}
|
||||
runningAbsDx += Math.abs(point.x - pX);
|
||||
runningAbsDy += Math.abs(point.y - pY);
|
||||
pX = point.x;
|
||||
pY = point.y;
|
||||
logDebug("(x, y, runningAbsDx, runningAbsDy) - (" + pX + ", " + pY + ", " + runningAbsDx
|
||||
+ ", " + runningAbsDy + ")");
|
||||
}
|
||||
|
||||
float devianceX = runningAbsDx - actualDx;
|
||||
float devianceY = runningAbsDy - actualDy;
|
||||
float distanceXIn = actualDx / getXdpi();
|
||||
float distanceYIn = actualDy / getYdpi();
|
||||
float totalDistanceIn = (float) Math
|
||||
.sqrt(distanceXIn * distanceXIn + distanceYIn * distanceYIn);
|
||||
|
||||
float maxXDeviance;
|
||||
float maxYDeviance;
|
||||
if (actualDx > actualDy) {
|
||||
maxXDeviance = MAX_X_PRIMARY_DEVIANCE * totalDistanceIn * getXdpi();
|
||||
maxYDeviance = MAX_Y_SECONDARY_DEVIANCE * totalDistanceIn * getYdpi();
|
||||
} else {
|
||||
maxXDeviance = MAX_X_SECONDARY_DEVIANCE * totalDistanceIn * getXdpi();
|
||||
maxYDeviance = MAX_Y_PRIMARY_DEVIANCE * totalDistanceIn * getYdpi();
|
||||
}
|
||||
|
||||
logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs "
|
||||
+ "(" + maxXDeviance + "," + maxYDeviance + ")");
|
||||
return devianceX > maxXDeviance || devianceY > maxYDeviance;
|
||||
}
|
||||
|
||||
private float getAtan2LastPoint() {
|
||||
MotionEvent firstEvent = getFirstMotionEvent();
|
||||
MotionEvent lastEvent = getLastMotionEvent();
|
||||
float offsetX = firstEvent.getX();
|
||||
float offsetY = firstEvent.getY();
|
||||
float lastX = lastEvent.getX() - offsetX;
|
||||
float lastY = lastEvent.getY() - offsetY;
|
||||
|
||||
return (float) Math.atan2(lastY, lastX);
|
||||
}
|
||||
|
||||
private List<Point> rotateVertical() {
|
||||
// Calculate the angle relative to the y axis.
|
||||
double angle = Math.PI / 2 - getAtan2LastPoint();
|
||||
logDebug("Rotating to vertical by: " + angle);
|
||||
return rotateMotionEvents(getRecentMotionEvents(), -angle);
|
||||
}
|
||||
|
||||
private List<Point> rotateHorizontal() {
|
||||
// Calculate the angle relative to the x axis.
|
||||
double angle = getAtan2LastPoint();
|
||||
logDebug("Rotating to horizontal by: " + angle);
|
||||
return rotateMotionEvents(getRecentMotionEvents(), angle);
|
||||
}
|
||||
|
||||
private List<Point> rotateMotionEvents(List<MotionEvent> motionEvents, double angle) {
|
||||
List<Point> points = new ArrayList<>();
|
||||
double cosAngle = Math.cos(angle);
|
||||
double sinAngle = Math.sin(angle);
|
||||
MotionEvent firstEvent = motionEvents.get(0);
|
||||
float offsetX = firstEvent.getX();
|
||||
float offsetY = firstEvent.getY();
|
||||
for (MotionEvent motionEvent : motionEvents) {
|
||||
float x = motionEvent.getX() - offsetX;
|
||||
float y = motionEvent.getY() - offsetY;
|
||||
double rotatedX = cosAngle * x + sinAngle * y + offsetX;
|
||||
double rotatedY = -sinAngle * x + cosAngle * y + offsetY;
|
||||
points.add(new Point((int) rotatedX, (int) rotatedY));
|
||||
}
|
||||
|
||||
MotionEvent lastEvent = motionEvents.get(motionEvents.size() - 1);
|
||||
Point firstPoint = points.get(0);
|
||||
Point lastPoint = points.get(points.size() - 1);
|
||||
logDebug(
|
||||
"Before: (" + firstEvent.getX() + "," + firstEvent.getY() + "), ("
|
||||
+ lastEvent.getX() + ","
|
||||
+ lastEvent.getY() + ")");
|
||||
logDebug(
|
||||
"After: (" + firstPoint.x + "," + firstPoint.y + "), (" + lastPoint.x + ","
|
||||
+ lastPoint.y
|
||||
+ ")");
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class DiagonalClassifierTest extends SysuiTestCase {
|
||||
|
||||
// Next variable is not actually five, but is very close. 5 degrees is currently the value
|
||||
// used in the diagonal classifier, so we want slightly less than that to deal with
|
||||
// floating point errors.
|
||||
private static final float FIVE_DEG_IN_RADIANS = (float) (4.99f / 360f * Math.PI * 2f);
|
||||
private static final float UP_IN_RADIANS = (float) (Math.PI / 2f);
|
||||
private static final float DOWN_IN_RADIANS = (float) (3 * Math.PI / 2f);
|
||||
private static final float RIGHT_IN_RADIANS = 0;
|
||||
private static final float LEFT_IN_RADIANS = (float) Math.PI;
|
||||
private static final float FORTY_FIVE_DEG_IN_RADIANS = (float) (Math.PI / 4);
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
private FalsingClassifier mClassifier;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mClassifier = new DiagonalClassifier(mDataProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_UnknownAngle() {
|
||||
when(mDataProvider.getAngle()).thenReturn(Float.MAX_VALUE);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_VerticalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_MostlyVerticalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS * 2);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_BarelyVerticalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
UP_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
DOWN_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS * 2);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_HorizontalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_MostlyHorizontalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_BarelyHorizontalSwipe() {
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
LEFT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
RIGHT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS * 2);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_AffordanceSwipe() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
LEFT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
// This classifier may return false for other angles, but these are the only
|
||||
// two that actually matter, as affordances generally only travel in these two directions.
|
||||
// We expect other classifiers to false in those cases, so it really doesn't matter what
|
||||
// we do here.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_DiagonalSwipe() {
|
||||
// Horizontal Swipes
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
// Vertical Swipes
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.getAngle()).thenReturn(
|
||||
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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 org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class DistanceClassifierTest extends SysuiTestCase {
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
private FalsingClassifier mClassifier;
|
||||
private List<MotionEvent> mMotionEvents = new ArrayList<>();
|
||||
|
||||
private static final float DPI = 100;
|
||||
private static final int SCREEN_SIZE = (int) (DPI * 10);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mDataProvider.getHeightPixels()).thenReturn(SCREEN_SIZE);
|
||||
when(mDataProvider.getWidthPixels()).thenReturn(SCREEN_SIZE);
|
||||
when(mDataProvider.getXdpi()).thenReturn(DPI);
|
||||
when(mDataProvider.getYdpi()).thenReturn(DPI);
|
||||
mClassifier = new DistanceClassifier(mDataProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_noPointer() {
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_fling() {
|
||||
MotionEvent motionEventA = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 1, 1, 0);
|
||||
MotionEvent motionEventB = MotionEvent.obtain(1, 2, MotionEvent.ACTION_MOVE, 1, 2, 0);
|
||||
MotionEvent motionEventC = MotionEvent.obtain(1, 3, MotionEvent.ACTION_UP, 1, 40, 0);
|
||||
|
||||
appendMotionEvent(motionEventA);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventB);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventC);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_flingShort() {
|
||||
MotionEvent motionEventA = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 1, 1, 0);
|
||||
MotionEvent motionEventB = MotionEvent.obtain(1, 2, MotionEvent.ACTION_MOVE, 1, 2, 0);
|
||||
MotionEvent motionEventC = MotionEvent.obtain(1, 3, MotionEvent.ACTION_UP, 1, 10, 0);
|
||||
|
||||
appendMotionEvent(motionEventA);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventB);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventC);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_flingSlowly() {
|
||||
// These events, in testing, result in a fling that falls just short of the threshold.
|
||||
MotionEvent motionEventA = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 1, 1, 0);
|
||||
MotionEvent motionEventB = MotionEvent.obtain(1, 2, MotionEvent.ACTION_MOVE, 1, 15, 0);
|
||||
MotionEvent motionEventC = MotionEvent.obtain(1, 3, MotionEvent.ACTION_MOVE, 1, 16, 0);
|
||||
MotionEvent motionEventD = MotionEvent.obtain(1, 300, MotionEvent.ACTION_MOVE, 1, 17, 0);
|
||||
MotionEvent motionEventE = MotionEvent.obtain(1, 301, MotionEvent.ACTION_MOVE, 1, 18, 0);
|
||||
MotionEvent motionEventF = MotionEvent.obtain(1, 500, MotionEvent.ACTION_UP, 1, 19, 0);
|
||||
|
||||
appendMotionEvent(motionEventA);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventB);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
appendMotionEvent(motionEventC);
|
||||
appendMotionEvent(motionEventD);
|
||||
appendMotionEvent(motionEventE);
|
||||
appendMotionEvent(motionEventF);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
motionEventD.recycle();
|
||||
motionEventE.recycle();
|
||||
motionEventF.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_swipe() {
|
||||
MotionEvent motionEventA = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 1, 1, 0);
|
||||
MotionEvent motionEventB = MotionEvent.obtain(1, 3, MotionEvent.ACTION_MOVE, 1, DPI * 3, 0);
|
||||
MotionEvent motionEventC = MotionEvent.obtain(1, 1000, MotionEvent.ACTION_UP, 1, DPI * 3,
|
||||
0);
|
||||
|
||||
appendMotionEvent(motionEventA);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
|
||||
appendMotionEvent(motionEventB);
|
||||
appendMotionEvent(motionEventC);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
motionEventA.recycle();
|
||||
motionEventB.recycle();
|
||||
motionEventC.recycle();
|
||||
}
|
||||
|
||||
private void appendMotionEvent(MotionEvent motionEvent) {
|
||||
if (mMotionEvents.isEmpty()) {
|
||||
when(mDataProvider.getFirstRecentMotionEvent()).thenReturn(motionEvent);
|
||||
}
|
||||
|
||||
mMotionEvents.add(motionEvent);
|
||||
when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
|
||||
|
||||
when(mDataProvider.getLastMotionEvent()).thenReturn(motionEvent);
|
||||
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
}
|
||||
}
|
||||
@@ -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(5 * 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.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 org.mockito.Mock;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class PointerCountClassifierTest extends SysuiTestCase {
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
private FalsingClassifier mClassifier;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mClassifier = new PointerCountClassifier(mDataProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_noPointer() {
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_singlePointer() {
|
||||
MotionEvent motionEvent = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 1, 1, 0);
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
motionEvent.recycle();
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_multiPointer() {
|
||||
MotionEvent.PointerProperties[] pointerProperties =
|
||||
MotionEvent.PointerProperties.createArray(2);
|
||||
pointerProperties[0].id = 0;
|
||||
pointerProperties[1].id = 1;
|
||||
MotionEvent.PointerCoords[] pointerCoords = MotionEvent.PointerCoords.createArray(2);
|
||||
MotionEvent motionEvent = MotionEvent.obtain(
|
||||
1, 1, MotionEvent.ACTION_DOWN, 2, pointerProperties, pointerCoords, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
0, 0);
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
motionEvent.recycle();
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.GENERIC;
|
||||
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
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 org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ProximityClassifierTest extends SysuiTestCase {
|
||||
|
||||
private static final long NS_PER_MS = 1000000;
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
@Mock
|
||||
private DistanceClassifier mDistanceClassifier;
|
||||
private FalsingClassifier mClassifier;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mDataProvider.getInteractionType()).thenReturn(GENERIC);
|
||||
when(mDistanceClassifier.isLongSwipe()).thenReturn(false);
|
||||
mClassifier = new ProximityClassifier(mDistanceClassifier, mDataProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_uncovered() {
|
||||
touchDown();
|
||||
touchUp(10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_mostlyUncovered() {
|
||||
touchDown();
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 1));
|
||||
mClassifier.onSensorEvent(createSensorEvent(false, 2));
|
||||
touchUp(20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_quickSettings() {
|
||||
touchDown();
|
||||
when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 1));
|
||||
mClassifier.onSensorEvent(createSensorEvent(false, 11));
|
||||
touchUp(10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_covered() {
|
||||
touchDown();
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 1));
|
||||
mClassifier.onSensorEvent(createSensorEvent(false, 11));
|
||||
touchUp(10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_mostlyCovered() {
|
||||
touchDown();
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 1));
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 95));
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 96));
|
||||
mClassifier.onSensorEvent(createSensorEvent(false, 100));
|
||||
touchUp(100);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_coveredWithLongSwipe() {
|
||||
touchDown();
|
||||
mClassifier.onSensorEvent(createSensorEvent(true, 1));
|
||||
mClassifier.onSensorEvent(createSensorEvent(false, 11));
|
||||
touchUp(10);
|
||||
when(mDistanceClassifier.isLongSwipe()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
private void touchDown() {
|
||||
MotionEvent motionEvent = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 0, 0, 0);
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
motionEvent.recycle();
|
||||
}
|
||||
|
||||
private void touchUp(long duration) {
|
||||
MotionEvent motionEvent = MotionEvent.obtain(1, 1 + duration, MotionEvent.ACTION_UP, 0,
|
||||
100, 0);
|
||||
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
|
||||
motionEvent.recycle();
|
||||
}
|
||||
|
||||
private SensorEvent createSensorEvent(boolean covered, long timestampMs) {
|
||||
SensorEvent sensorEvent = Mockito.mock(SensorEvent.class);
|
||||
Sensor sensor = Mockito.mock(Sensor.class);
|
||||
when(sensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY);
|
||||
when(sensor.getMaximumRange()).thenReturn(1f);
|
||||
sensorEvent.sensor = sensor;
|
||||
sensorEvent.timestamp = timestampMs * NS_PER_MS;
|
||||
try {
|
||||
Field valuesField = SensorEvent.class.getField("values");
|
||||
valuesField.setAccessible(true);
|
||||
float[] sensorValue = {covered ? 0 : 1};
|
||||
try {
|
||||
valuesField.set(sensorEvent, sensorValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return sensorEvent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* 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 com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
|
||||
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
|
||||
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
|
||||
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
|
||||
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
|
||||
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
|
||||
import static com.android.systemui.classifier.Classifier.UNLOCK;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class TypeClassifierTest extends SysuiTestCase {
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
|
||||
private FalsingClassifier mClassifier;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mClassifier = new TypeClassifier(mDataProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_QuickSettings() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_QuickSettings() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_PulseExpand() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(PULSE_EXPAND);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_PulseExpand() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(PULSE_EXPAND);
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_NotificationDragDown() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DRAG_DOWN);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_NotificationDragDown() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DRAG_DOWN);
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_NotificationDismiss() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DISMISS);
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_NotificationDismiss() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DISMISS);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPass_Unlock() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(UNLOCK);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_Unlock() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(UNLOCK);
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_BouncerUnlock() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(BOUNCER_UNLOCK);
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_BouncerUnlock() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(BOUNCER_UNLOCK);
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_LeftAffordance() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false); // vertical should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_LeftAffordance() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_RightAffordance() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(false);
|
||||
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(false); // vertical should cause no effect.
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
when(mDataProvider.isVertical()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFalse_RightAffordance() {
|
||||
when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(true);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
|
||||
when(mDataProvider.isUp()).thenReturn(false);
|
||||
when(mDataProvider.isRight()).thenReturn(true);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* 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.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ZigZagClassifierTest extends SysuiTestCase {
|
||||
|
||||
private static final long NS_PER_MS = 1000000;
|
||||
|
||||
@Mock
|
||||
private FalsingDataProvider mDataProvider;
|
||||
private FalsingClassifier mClassifier;
|
||||
private List<MotionEvent> mMotionEvents = new ArrayList<>();
|
||||
private float mOffsetX = 0;
|
||||
private float mOffsetY = 0;
|
||||
private float mDx;
|
||||
private float mDy;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mDataProvider.getXdpi()).thenReturn(100f);
|
||||
when(mDataProvider.getYdpi()).thenReturn(100f);
|
||||
when(mDataProvider.getRecentMotionEvents()).thenReturn(mMotionEvents);
|
||||
mClassifier = new ZigZagClassifier(mDataProvider);
|
||||
|
||||
|
||||
// Calculate the response to these calls on the fly, otherwise Mockito gets bogged down
|
||||
// everytime we call appendMotionEvent.
|
||||
when(mDataProvider.getFirstRecentMotionEvent()).thenAnswer(
|
||||
(Answer<MotionEvent>) invocation -> mMotionEvents.get(0));
|
||||
when(mDataProvider.getLastMotionEvent()).thenAnswer(
|
||||
(Answer<MotionEvent>) invocation -> mMotionEvents.get(mMotionEvents.size() - 1));
|
||||
when(mDataProvider.isHorizontal()).thenAnswer(
|
||||
(Answer<Boolean>) invocation -> Math.abs(mDy) < Math.abs(mDx));
|
||||
when(mDataProvider.isVertical()).thenAnswer(
|
||||
(Answer<Boolean>) invocation -> Math.abs(mDy) > Math.abs(mDx));
|
||||
when(mDataProvider.isRight()).thenAnswer((Answer<Boolean>) invocation -> mDx > 0);
|
||||
when(mDataProvider.isUp()).thenAnswer((Answer<Boolean>) invocation -> mDy < 0);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
for (MotionEvent motionEvent : mMotionEvents) {
|
||||
motionEvent.recycle();
|
||||
}
|
||||
mMotionEvents.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_fewTouchesVertical() {
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
appendMotionEvent(0, 0);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
appendMotionEvent(0, 100);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_vertical() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(0, 100);
|
||||
appendMotionEvent(0, 200);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_fewTouchesHorizontal() {
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
appendMotionEvent(0, 0);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
appendMotionEvent(100, 0);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_horizontal() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 0);
|
||||
appendMotionEvent(200, 0);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFail_minimumTouchesVertical() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(0, 100);
|
||||
appendMotionEvent(0, 1);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_minimumTouchesHorizontal() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 0);
|
||||
appendMotionEvent(1, 0);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_fortyFiveDegreesStraight() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(10, 10);
|
||||
appendMotionEvent(20, 20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_horizontalZigZagVerticalStraight() {
|
||||
// This test looks just like testFail_horizontalZigZagVerticalStraight but with
|
||||
// a longer y range, making it look straighter.
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(5, 100);
|
||||
appendMotionEvent(-5, 200);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass_horizontalStraightVerticalZigZag() {
|
||||
// This test looks just like testFail_horizontalStraightVerticalZigZag but with
|
||||
// a longer x range, making it look straighter.
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 5);
|
||||
appendMotionEvent(200, -5);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_horizontalZigZagVerticalStraight() {
|
||||
// This test looks just like testPass_horizontalZigZagVerticalStraight but with
|
||||
// a shorter y range, making it look more crooked.
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(5, 10);
|
||||
appendMotionEvent(-5, 20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFail_horizontalStraightVerticalZigZag() {
|
||||
// This test looks just like testPass_horizontalStraightVerticalZigZag but with
|
||||
// a shorter x range, making it look more crooked.
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(10, 5);
|
||||
appendMotionEvent(20, -5);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between0And45() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 5);
|
||||
appendMotionEvent(200, 10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 0);
|
||||
appendMotionEvent(200, 10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, -10);
|
||||
appendMotionEvent(200, 10);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, -10);
|
||||
appendMotionEvent(200, 50);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between45And90() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(10, 50);
|
||||
appendMotionEvent(8, 100);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(1, 800);
|
||||
appendMotionEvent(2, 900);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-10, 600);
|
||||
appendMotionEvent(30, 700);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(40, 100);
|
||||
appendMotionEvent(0, 101);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between90And135() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-10, 50);
|
||||
appendMotionEvent(-24, 100);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-20, 800);
|
||||
appendMotionEvent(-20, 900);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(30, 600);
|
||||
appendMotionEvent(-10, 700);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-80, 100);
|
||||
appendMotionEvent(-10, 101);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between135And180() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-120, 10);
|
||||
appendMotionEvent(-200, 20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-20, 8);
|
||||
appendMotionEvent(-40, 2);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-500, -2);
|
||||
appendMotionEvent(-600, 70);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-80, 100);
|
||||
appendMotionEvent(-100, 1);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between180And225() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-120, -10);
|
||||
appendMotionEvent(-200, -20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-20, -8);
|
||||
appendMotionEvent(-40, -2);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-500, 2);
|
||||
appendMotionEvent(-600, -70);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-80, -100);
|
||||
appendMotionEvent(-100, -1);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between225And270() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-12, -20);
|
||||
appendMotionEvent(-20, -40);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-20, -130);
|
||||
appendMotionEvent(-40, -260);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(1, -100);
|
||||
appendMotionEvent(-6, -200);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-80, -100);
|
||||
appendMotionEvent(-10, -110);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between270And315() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(12, -20);
|
||||
appendMotionEvent(20, -40);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(20, -130);
|
||||
appendMotionEvent(40, -260);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(-1, -100);
|
||||
appendMotionEvent(6, -200);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(80, -100);
|
||||
appendMotionEvent(10, -110);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_between315And360() {
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(120, -20);
|
||||
appendMotionEvent(200, -40);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(200, -13);
|
||||
appendMotionEvent(400, -30);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(100, 10);
|
||||
appendMotionEvent(600, -20);
|
||||
assertThat(mClassifier.isFalseTouch(), is(false));
|
||||
|
||||
mMotionEvents.clear();
|
||||
appendMotionEvent(0, 0);
|
||||
appendMotionEvent(80, -100);
|
||||
appendMotionEvent(100, -1);
|
||||
assertThat(mClassifier.isFalseTouch(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_randomOrigins() {
|
||||
// The purpose of this test is to try all the other tests from different starting points.
|
||||
// We use a pre-determined seed to make this test repeatable.
|
||||
Random rand = new Random(23);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
mOffsetX = rand.nextInt(2000) - 1000;
|
||||
mOffsetY = rand.nextInt(2000) - 1000;
|
||||
try {
|
||||
mMotionEvents.clear();
|
||||
testPass_fewTouchesVertical();
|
||||
mMotionEvents.clear();
|
||||
testPass_vertical();
|
||||
mMotionEvents.clear();
|
||||
testFail_horizontalStraightVerticalZigZag();
|
||||
mMotionEvents.clear();
|
||||
testFail_horizontalZigZagVerticalStraight();
|
||||
mMotionEvents.clear();
|
||||
testFail_minimumTouchesHorizontal();
|
||||
mMotionEvents.clear();
|
||||
testFail_minimumTouchesVertical();
|
||||
mMotionEvents.clear();
|
||||
testPass_fewTouchesHorizontal();
|
||||
mMotionEvents.clear();
|
||||
testPass_fortyFiveDegreesStraight();
|
||||
mMotionEvents.clear();
|
||||
testPass_horizontal();
|
||||
mMotionEvents.clear();
|
||||
testPass_horizontalStraightVerticalZigZag();
|
||||
mMotionEvents.clear();
|
||||
testPass_horizontalZigZagVerticalStraight();
|
||||
mMotionEvents.clear();
|
||||
test_between0And45();
|
||||
mMotionEvents.clear();
|
||||
test_between45And90();
|
||||
mMotionEvents.clear();
|
||||
test_between90And135();
|
||||
mMotionEvents.clear();
|
||||
test_between135And180();
|
||||
mMotionEvents.clear();
|
||||
test_between180And225();
|
||||
mMotionEvents.clear();
|
||||
test_between225And270();
|
||||
mMotionEvents.clear();
|
||||
test_between270And315();
|
||||
mMotionEvents.clear();
|
||||
test_between315And360();
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Random origin failure in iteration " + i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void appendMotionEvent(float x, float y) {
|
||||
x += mOffsetX;
|
||||
y += mOffsetY;
|
||||
|
||||
long eventTime = mMotionEvents.size() + 1;
|
||||
MotionEvent motionEvent = MotionEvent.obtain(1, eventTime, MotionEvent.ACTION_DOWN, x, y,
|
||||
0);
|
||||
mMotionEvents.add(motionEvent);
|
||||
|
||||
mDx = mDataProvider.getFirstRecentMotionEvent().getX()
|
||||
- mDataProvider.getLastMotionEvent().getX();
|
||||
mDy = mDataProvider.getFirstRecentMotionEvent().getY()
|
||||
- mDataProvider.getLastMotionEvent().getY();
|
||||
|
||||
mClassifier.onTouchEvent(motionEvent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user