diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index bd1571aea4f4d..ca20eb019a222 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -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() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java new file mode 100644 index 0000000000000..730907e1fa9cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java index 10b34c08634bd..a329148cbc411 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java @@ -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; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java new file mode 100644 index 0000000000000..ade5f36d659e0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java @@ -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)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java index 5ffaf75d6376c..1da42061c234e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/FalsingDataProviderTest.java @@ -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();