Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d8efd0d4 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add DiagonalClassifier to the BrightLineFalsingManager.

This rejects swipes that are too close to 45 degrees.

Bug: 111394067
Test: atest SystemUITests
Change-Id: I45913918e89b965678628e3a6a0431a3db4b085a
parent fd42bdbb
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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() {
+89 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -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;
            }
        }
    }

+213 −0
Original line number Diff line number Diff line
/*
 * 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));
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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();