Add DiagonalClassifier to the BrightLineFalsingManager.

This rejects swipes that are too close to 45 degrees.

Bug: 111394067
Test: atest SystemUITests
Change-Id: I45913918e89b965678628e3a6a0431a3db4b085a
This commit is contained in:
Dave Mankoff
2019-06-11 16:48:34 -04:00
parent fd42bdbb86
commit d8efd0d4ed
5 changed files with 317 additions and 1 deletions

View File

@@ -66,6 +66,7 @@ public class BrightLineFalsingManager implements FalsingManager {
mClassifiers = new ArrayList<>();
mClassifiers.add(new PointerCountClassifier(mDataProvider));
mClassifiers.add(new TypeClassifier(mDataProvider));
mClassifiers.add(new DiagonalClassifier(mDataProvider));
}
private void registerSensors() {

View File

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

View File

@@ -33,6 +33,8 @@ import java.util.List;
class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
final int mWidthPixels;
final int mHeightPixels;
final float mXdpi;
@@ -113,6 +115,11 @@ class FalsingDataProvider {
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;
@@ -159,6 +166,12 @@ class FalsingDataProvider {
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;
}
}
}

View File

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

View File

@@ -168,7 +168,7 @@ public class FalsingDataProviderTest extends SysuiTestCase {
MotionEvent motionEventB = obtainMotionEvent(MotionEvent.ACTION_MOVE, 2, -1, -1);
mDataProvider.onMotionEvent(motionEventOrigin);
mDataProvider.onMotionEvent(motionEventB);
assertThat((double) mDataProvider.getAngle(), closeTo(-3 * Math.PI / 4, .001));
assertThat((double) mDataProvider.getAngle(), closeTo(5 * Math.PI / 4, .001));
motionEventB.recycle();
mDataProvider.onSessionEnd();