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
This commit is contained in:
Dave Mankoff
2019-06-12 17:58:30 -04:00
parent 65b5769c07
commit 8bfbe3348e
3 changed files with 298 additions and 1 deletions

View File

@@ -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() {

View File

@@ -0,0 +1,134 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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;
}
}