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

Commit 8bfbe334 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add ProximityClassifier to the BrightLineFalsingManager

This requires swipes to travel a minimum distance and/or
fling a minimum distance.

Bug: 111394067
Test: atest SystemUITests
Change-Id: Id7586011a30fdcd9dfef7c937f22c33564829307
parent 65b5769c
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -64,10 +64,14 @@ public class BrightLineFalsingManager implements FalsingManager {
        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(new DistanceClassifier(mDataProvider));
        mClassifiers.add(distanceClassifier);
        mClassifiers.add(proximityClassifier);
    }

    private void registerSensors() {
+134 −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.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;
    }
}
+159 −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.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;
    }
}