Merge "Adding isFalseLongTap method" into tm-qpr-dev am: 4562cb6733

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20190664

Change-Id: I5952444a9f50ad5b580ce52c2c35ad55708d702a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Michael Mikhail
2022-11-04 04:25:14 +00:00
committed by Automerger Merge Worker
11 changed files with 273 additions and 55 deletions

View File

@@ -81,6 +81,18 @@ public interface FalsingManager {
*/
boolean isFalseTap(@Penalty int penalty);
/**
* Returns true if the FalsingManager thinks the last gesture was not a valid long tap.
*
* Use this method to validate a long tap for launching an action, like long press on a UMO
*
* The only parameter, penalty, indicates how much this should affect future gesture
* classifications if this long tap looks like a false.
* As long taps are hard to confirm as false or otherwise,
* a low penalty value is encouraged unless context indicates otherwise.
*/
boolean isFalseLongTap(@Penalty int penalty);
/**
* Returns true if the last two gestures do not look like a double tap.
*

View File

@@ -34,6 +34,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
import com.android.systemui.classifier.HistoryTracker.BeliefListener;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -65,6 +67,7 @@ public class BrightLineFalsingManager implements FalsingManager {
private static final double FALSE_BELIEF_THRESHOLD = 0.9;
private final FalsingDataProvider mDataProvider;
private final LongTapClassifier mLongTapClassifier;
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
@@ -73,6 +76,7 @@ public class BrightLineFalsingManager implements FalsingManager {
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
private FeatureFlags mFeatureFlags;
private static final Queue<String> RECENT_INFO_LOG =
new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1);
private static final Queue<DebugSwipeRecord> RECENT_SWIPES =
@@ -175,19 +179,23 @@ public class BrightLineFalsingManager implements FalsingManager {
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker,
KeyguardStateController keyguardStateController,
AccessibilityManager accessibilityManager,
@TestHarness boolean testHarness) {
@TestHarness boolean testHarness,
FeatureFlags featureFlags) {
mDataProvider = falsingDataProvider;
mMetricsLogger = metricsLogger;
mClassifiers = classifiers;
mSingleTapClassifier = singleTapClassifier;
mLongTapClassifier = longTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
mKeyguardStateController = keyguardStateController;
mAccessibilityManager = accessibilityManager;
mTestHarness = testHarness;
mFeatureFlags = featureFlags;
mDataProvider.addSessionListener(mSessionListener);
mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
@@ -312,6 +320,58 @@ public class BrightLineFalsingManager implements FalsingManager {
}
@Override
public boolean isFalseLongTap(@Penalty int penalty) {
if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
return false;
}
checkDestroyed();
if (skipFalsing(GENERIC)) {
mPriorResults = getPassedResult(1);
logDebug("Skipped falsing");
return false;
}
double falsePenalty = 0;
switch(penalty) {
case NO_PENALTY:
falsePenalty = 0;
break;
case LOW_PENALTY:
falsePenalty = 0.1;
break;
case MODERATE_PENALTY:
falsePenalty = 0.3;
break;
case HIGH_PENALTY:
falsePenalty = 0.6;
break;
}
FalsingClassifier.Result longTapResult =
mLongTapClassifier.isTap(mDataProvider.getRecentMotionEvents().isEmpty()
? mDataProvider.getPriorMotionEvents()
: mDataProvider.getRecentMotionEvents(), falsePenalty);
mPriorResults = Collections.singleton(longTapResult);
if (!longTapResult.isFalse()) {
if (mDataProvider.isJustUnlockedWithFace()) {
// Immediately pass if a face is detected.
mPriorResults = getPassedResult(1);
logDebug("False Long Tap: false (face detected)");
} else {
mPriorResults = getPassedResult(0.1);
logDebug("False Long Tap: false (default)");
}
return false;
} else {
logDebug("False Long Tap: " + longTapResult.isFalse() + " (simple)");
return longTapResult.isFalse();
}
}
@Override
public boolean isFalseDoubleTap() {
checkDestroyed();

View File

@@ -138,6 +138,11 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable {
return mInternalFalsingManager.isFalseTap(penalty);
}
@Override
public boolean isFalseLongTap(int penalty) {
return mInternalFalsingManager.isFalseLongTap(penalty);
}
@Override
public boolean isFalseDoubleTap() {
return mInternalFalsingManager.isFalseDoubleTap();

View File

@@ -40,6 +40,7 @@ import dagger.multibindings.ElementsIntoSet;
public interface FalsingModule {
String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop";
String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop";
String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
@@ -81,4 +82,11 @@ public interface FalsingModule {
static float providesSingleTapTouchSlop(ViewConfiguration viewConfiguration) {
return viewConfiguration.getScaledTouchSlop();
}
/** */
@Provides
@Named(LONG_TAP_TOUCH_SLOP)
static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) {
return viewConfiguration.getScaledTouchSlop() * 1.25f;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2022 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;
import static com.android.systemui.classifier.FalsingModule.LONG_TAP_TOUCH_SLOP;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Falsing classifier that accepts or rejects a gesture as a long tap.
*/
public class LongTapClassifier extends TapClassifier{
@Inject
LongTapClassifier(FalsingDataProvider dataProvider,
@Named(LONG_TAP_TOUCH_SLOP) float touchSlop) {
super(dataProvider, touchSlop);
}
}

View File

@@ -18,57 +18,17 @@ package com.android.systemui.classifier;
import static com.android.systemui.classifier.FalsingModule.SINGLE_TAP_TOUCH_SLOP;
import android.view.MotionEvent;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Falsing classifier that accepts or rejects a single gesture as a tap.
* Falsing classifier that accepts or rejects a gesture as a single tap.
*/
public class SingleTapClassifier extends FalsingClassifier {
private final float mTouchSlop;
public class SingleTapClassifier extends TapClassifier {
@Inject
SingleTapClassifier(FalsingDataProvider dataProvider,
@Named(SINGLE_TAP_TOUCH_SLOP) float touchSlop) {
super(dataProvider);
mTouchSlop = touchSlop;
}
@Override
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
return isTap(getRecentMotionEvents(), 0.5);
}
/** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
if (motionEvents.isEmpty()) {
return falsed(0, "no motion events");
}
float downX = motionEvents.get(0).getX();
float downY = motionEvents.get(0).getY();
for (MotionEvent event : motionEvents) {
String reason;
if (Math.abs(event.getX() - downX) >= mTouchSlop) {
reason = "dX too big for a tap: "
+ Math.abs(event.getX() - downX)
+ "vs "
+ mTouchSlop;
return falsed(falsePenalty, reason);
} else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
reason = "dY too big for a tap: "
+ Math.abs(event.getY() - downY)
+ " vs "
+ mTouchSlop;
return falsed(falsePenalty, reason);
}
}
return Result.passed(0);
super(dataProvider, touchSlop);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 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;
import android.view.MotionEvent;
import java.util.List;
/**
* Falsing classifier that accepts or rejects a gesture as a tap.
*/
public abstract class TapClassifier extends FalsingClassifier{
private final float mTouchSlop;
TapClassifier(FalsingDataProvider dataProvider,
float touchSlop) {
super(dataProvider);
mTouchSlop = touchSlop;
}
@Override
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
return isTap(getRecentMotionEvents(), 0.5);
}
/** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
if (motionEvents.isEmpty()) {
return falsed(0, "no motion events");
}
float downX = motionEvents.get(0).getX();
float downY = motionEvents.get(0).getY();
for (MotionEvent event : motionEvents) {
String reason;
if (Math.abs(event.getX() - downX) >= mTouchSlop) {
reason = "dX too big for a tap: "
+ Math.abs(event.getX() - downX)
+ "vs "
+ mTouchSlop;
return falsed(falsePenalty, reason);
} else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
reason = "dY too big for a tap: "
+ Math.abs(event.getY() - downY)
+ " vs "
+ mTouchSlop;
return falsed(falsePenalty, reason);
}
}
return Result.passed(0);
}
}

View File

@@ -349,6 +349,9 @@ object Flags {
// 2000 - device controls
@Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100)
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |

View File

@@ -39,6 +39,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -66,6 +68,8 @@ public class BrightLineClassifierTest extends SysuiTestCase {
@Mock
private SingleTapClassifier mSingleTapClassfier;
@Mock
private LongTapClassifier mLongTapClassifier;
@Mock
private DoubleTapClassifier mDoubleTapClassifier;
@Mock
private FalsingClassifier mClassifierA;
@@ -80,6 +84,7 @@ public class BrightLineClassifierTest extends SysuiTestCase {
private AccessibilityManager mAccessibilityManager;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
@@ -94,6 +99,7 @@ public class BrightLineClassifierTest extends SysuiTestCase {
when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mPassedResult);
when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mPassedResult);
when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mPassedResult);
mClassifiers.add(mClassifierA);
@@ -101,9 +107,9 @@ public class BrightLineClassifierTest extends SysuiTestCase {
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
false);
mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
mAccessibilityManager, false, mFakeFeatureFlags);
ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
@@ -113,6 +119,7 @@ public class BrightLineClassifierTest extends SysuiTestCase {
gestureCompleteListenerCaptor.capture());
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
}
@Test
@@ -212,7 +219,7 @@ public class BrightLineClassifierTest extends SysuiTestCase {
}
@Test
public void testIsFalseTap_EmptyRecentEvents() {
public void testIsFalseSingleTap_EmptyRecentEvents() {
// Ensure we look at prior events if recent events has already been emptied.
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
@@ -223,7 +230,7 @@ public class BrightLineClassifierTest extends SysuiTestCase {
@Test
public void testIsFalseTap_RobustCheck_NoFaceAuth() {
public void testIsFalseSingleTap_RobustCheck_NoFaceAuth() {
when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
@@ -233,12 +240,49 @@ public class BrightLineClassifierTest extends SysuiTestCase {
}
@Test
public void testIsFalseTap_RobustCheck_FaceAuth() {
public void testIsFalseSingleTap_RobustCheck_FaceAuth() {
when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTap(NO_PENALTY)).isFalse();
}
@Test
public void testIsFalseLongTap_EmptyRecentEvents() {
// Ensure we look at prior events if recent events has already been emptied.
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
mBrightLineFalsingManager.isFalseLongTap(0);
verify(mLongTapClassifier).isTap(mMotionEventList, 0);
}
@Test
public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
}
@Test
public void testIsFalseLongTap_FalseLongTap() {
when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
}
@Test
public void testIsFalseLongTap_RobustCheck_NoFaceAuth() {
when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(false);
assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
}
@Test
public void testIsFalseLongTap_RobustCheck_FaceAuth() {
when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
}
@Test
public void testIsFalseDoubleTap() {
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))

View File

@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -57,6 +59,8 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
@Mock
private SingleTapClassifier mSingleTapClassifier;
@Mock
private LongTapClassifier mLongTapClassifier;
@Mock
private DoubleTapClassifier mDoubleTapClassifier;
@Mock
private FalsingClassifier mClassifierA;
@@ -71,6 +75,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
@Before
public void setup() {
@@ -78,15 +83,17 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
when(mSingleTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
mClassifiers.add(mClassifierA);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassifier, mDoubleTapClassifier,
mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
false);
mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
mAccessibilityManager, false, mFakeFeatureFlags);
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
}
@Test
@@ -104,6 +111,13 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
}
@Test
public void testA11yDisablesLongTap() {
assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isFalse();
}
@Test
public void testA11yDisablesDoubleTap() {
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();

View File

@@ -39,6 +39,7 @@ public class FalsingManagerFake implements FalsingManager {
private boolean mShouldEnforceBouncer;
private boolean mIsReportingEnabled;
private boolean mIsFalseRobustTap;
private boolean mIsFalseLongTap;
private boolean mDestroyed;
private boolean mIsProximityNear;
@@ -87,6 +88,10 @@ public class FalsingManagerFake implements FalsingManager {
mIsProximityNear = proxNear;
}
public void setFalseLongTap(boolean falseLongTap) {
mIsFalseLongTap = falseLongTap;
}
@Override
public boolean isSimpleTap() {
checkDestroyed();
@@ -99,6 +104,12 @@ public class FalsingManagerFake implements FalsingManager {
return mIsFalseRobustTap;
}
@Override
public boolean isFalseLongTap(int penalty) {
checkDestroyed();
return mIsFalseLongTap;
}
@Override
public boolean isFalseDoubleTap() {
checkDestroyed();