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:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user