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