Merge changes from topic "b172655679-tap-falsing"

* changes:
  Connect FalsingManager to HistoryTracker.
  Add ability to ignore gestures in FalsingManager
This commit is contained in:
Dave Mankoff
2021-01-06 14:42:05 +00:00
committed by Android (Google) Code Review
14 changed files with 341 additions and 87 deletions

View File

@@ -23,10 +23,16 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.android.systemui.Gefingerpoken;
import java.util.ArrayList;
import java.util.List;
/**
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
private final List<Gefingerpoken> mMotionEventListener = new ArrayList<>();
public KeyguardInputView(Context context) {
super(context);
@@ -56,4 +62,25 @@ public abstract class KeyguardInputView extends LinearLayout {
boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
}
void addMotionEventListener(Gefingerpoken listener) {
mMotionEventListener.add(listener);
}
void removeMotionEventListener(Gefingerpoken listener) {
mMotionEventListener.remove(listener);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mMotionEventListener.stream().anyMatch(listener -> listener.onTouchEvent(event))
|| super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mMotionEventListener.stream().anyMatch(
listener -> listener.onInterceptTouchEvent(event))
|| super.onInterceptTouchEvent(event);
}
}

View File

@@ -26,6 +26,7 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -155,6 +156,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final Resources mResources;
private LiftToActivateListener mLiftToActivateListener;
private TelephonyManager mTelephonyManager;
private final FalsingCollector mFalsingCollector;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -163,7 +165,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
@Main Resources resources, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager) {
TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -173,6 +175,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mResources = resources;
mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
mFalsingCollector = falsingCollector;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -191,17 +194,17 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener);
mLiftToActivateListener, mFalsingCollector);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager);
mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager);
mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);

View File

@@ -25,12 +25,15 @@ import android.view.View.OnTouchListener;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
protected PasswordTextView mPasswordEntry;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -40,13 +43,26 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
return false;
};
private final OnTouchListener mOnTouchListener = (v, event) -> {
private final OnTouchListener mActionButtonTouchListener = (v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mView.doHapticKeyClick();
}
return false;
};
private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mFalsingCollector.avoidGesture();
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
};
protected KeyguardPinBasedInputViewController(T view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode,
@@ -54,10 +70,12 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener) {
LiftToActivateListener liftToActivateListener,
FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
}
@@ -65,11 +83,13 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
protected void onViewAttached() {
super.onViewAttached();
mView.addMotionEventListener(mGlobalTouchListener);
mPasswordEntry.setOnKeyListener(mOnKeyListener);
mPasswordEntry.setUserActivityListener(this::onUserInput);
View deleteButton = mView.findViewById(R.id.delete_button);
deleteButton.setOnTouchListener(mOnTouchListener);
deleteButton.setOnTouchListener(mActionButtonTouchListener);
deleteButton.setOnClickListener(v -> {
// check for time-based lockouts
if (mPasswordEntry.isEnabled()) {
@@ -87,7 +107,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
View okButton = mView.findViewById(R.id.key_enter);
if (okButton != null) {
okButton.setOnTouchListener(mOnTouchListener);
okButton.setOnTouchListener(mActionButtonTouchListener);
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -100,6 +120,12 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
}
}
@Override
protected void onViewDetached() {
super.onViewDetached();
mView.removeMotionEventListener(mGlobalTouchListener);
}
@Override
public void onResume(int reason) {
super.onResume(reason);

View File

@@ -22,6 +22,7 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
public class KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -32,10 +33,11 @@ public class KeyguardPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener) {
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener);
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}

View File

@@ -38,6 +38,7 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -76,11 +77,11 @@ public class KeyguardSimPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager) {
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener);
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);

View File

@@ -39,6 +39,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -83,11 +84,11 @@ public class KeyguardSimPukViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager) {
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener);
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);

View File

@@ -18,6 +18,7 @@ package com.android.systemui.classifier;
import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS;
import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TIMEOUT_MS;
import android.net.Uri;
import android.os.Build;
@@ -28,23 +29,26 @@ import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
@@ -65,7 +69,8 @@ public class BrightLineFalsingManager implements FalsingManager {
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
private final SystemClock mSystemClock;
private final DelayableExecutor mDelayableExecutor;
private final long mDoubleTapTimeMs;
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -90,23 +95,44 @@ public class BrightLineFalsingManager implements FalsingManager {
private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
new FalsingDataProvider.GestureCompleteListener() {
@Override
public void onGestureComplete() {
mHistoryTracker.addResults(
mClassifiers.stream().map(FalsingClassifier::classifyGesture)
.collect(Collectors.toCollection(ArrayList::new)),
mSystemClock.uptimeMillis());
@Override
public void onGestureComplete(long completionTimeMs) {
if (mPriorResults != null) {
// Single taps that may become double taps don't get added right away.
if (mClassifyAsSingleTap) {
Collection<FalsingClassifier.Result> singleTapResults = mPriorResults;
mSingleTapHistoryCanceller = mDelayableExecutor.executeDelayed(
() -> {
mSingleTapHistoryCanceller = null;
mHistoryTracker.addResults(singleTapResults, completionTimeMs);
},
mDoubleTapTimeMs);
mClassifyAsSingleTap = false; // Don't treat things as single taps by default.
} else {
mHistoryTracker.addResults(mPriorResults, completionTimeMs);
}
mPriorResults = null;
} else {
// Gestures that were not classified get treated as a false.
mHistoryTracker.addResults(
Collections.singleton(
FalsingClassifier.Result.falsed(.8, "unclassified")),
completionTimeMs);
}
}
};
private boolean mPreviousResult = false;
private Collection<FalsingClassifier.Result> mPriorResults;
private boolean mClassifyAsSingleTap;
private Runnable mSingleTapHistoryCanceller;
@Inject
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
DockManager dockManager, MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
HistoryTracker historyTracker, SystemClock systemClock,
HistoryTracker historyTracker, @Main DelayableExecutor delayableExecutor,
@Named(DOUBLE_TAP_TIMEOUT_MS) long doubleTapTimeMs,
@TestHarness boolean testHarness) {
mDataProvider = falsingDataProvider;
mDockManager = dockManager;
@@ -115,7 +141,8 @@ public class BrightLineFalsingManager implements FalsingManager {
mSingleTapClassifier = singleTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
mSystemClock = systemClock;
mDelayableExecutor = delayableExecutor;
mDoubleTapTimeMs = doubleTapTimeMs;
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
@@ -129,38 +156,51 @@ public class BrightLineFalsingManager implements FalsingManager {
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
boolean result;
mClassifyAsSingleTap = false;
mDataProvider.setInteractionType(interactionType);
if (!mDataProvider.isDirty()) {
return mPreviousResult;
if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
Stream<FalsingClassifier.Result> results =
mClassifiers.stream().map(falsingClassifier -> {
FalsingClassifier.Result classifierResult =
falsingClassifier.classifyGesture(
mHistoryTracker.falsePenalty(),
mHistoryTracker.falseConfidence());
if (classifierResult.isFalse()) {
logInfo(String.format(
(Locale) null,
"{classifier=%s, interactionType=%d}",
falsingClassifier.getClass().getName(),
mDataProvider.getInteractionType()));
String reason = classifierResult.getReason();
if (reason != null) {
logInfo(reason);
}
} else {
logDebug(falsingClassifier.getClass().getName() + ": false");
}
return classifierResult;
});
mPriorResults = new ArrayList<>();
final boolean[] localResult = {false};
results.forEach(classifierResult -> {
localResult[0] |= classifierResult.isFalse();
mPriorResults.add(classifierResult);
});
result = localResult[0];
} else {
result = false;
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
}
mPreviousResult = !mTestHarness
&& !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()
&& mClassifiers.stream().anyMatch(falsingClassifier -> {
FalsingClassifier.Result result = falsingClassifier.classifyGesture(
mHistoryTracker.falsePenalty(), mHistoryTracker.falseConfidence());
if (result.isFalse()) {
logInfo(String.format(
(Locale) null,
"{classifier=%s, interactionType=%d}",
falsingClassifier.getClass().getName(),
mDataProvider.getInteractionType()));
String reason = result.getReason();
if (reason != null) {
logInfo(reason);
}
} else {
logDebug(falsingClassifier.getClass().getName() + ": false");
}
return result.isFalse();
});
logDebug("Is false touch? " + mPreviousResult);
logDebug("Is false touch? " + result);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
// Copy motion events, as the passed in list gets emptied out elsewhere in the code.
RECENT_SWIPES.add(new DebugSwipeRecord(
mPreviousResult,
result,
mDataProvider.getInteractionType(),
mDataProvider.getRecentMotionEvents().stream().map(
motionEvent -> new XYDt(
@@ -173,13 +213,16 @@ public class BrightLineFalsingManager implements FalsingManager {
}
}
return mPreviousResult;
return result;
}
@Override
public boolean isFalseTap(boolean robustCheck) {
mClassifyAsSingleTap = true;
FalsingClassifier.Result singleTapResult =
mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
mPriorResults = Collections.singleton(singleTapResult);
if (singleTapResult.isFalse()) {
logInfo(String.format(
(Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
@@ -192,7 +235,12 @@ public class BrightLineFalsingManager implements FalsingManager {
// TODO(b/172655679): More heuristics to come. For now, allow touches through if face-authed
if (robustCheck) {
return !mDataProvider.isJustUnlockedWithFace();
boolean result = !mDataProvider.isJustUnlockedWithFace();
mPriorResults = Collections.singleton(
result ? FalsingClassifier.Result.falsed(0.1, "no face detected")
: FalsingClassifier.Result.passed(1));
return result;
}
return false;
@@ -200,7 +248,9 @@ public class BrightLineFalsingManager implements FalsingManager {
@Override
public boolean isFalseDoubleTap() {
mClassifyAsSingleTap = false;
FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
mPriorResults = Collections.singleton(result);
if (result.isFalse()) {
logInfo(String.format(
(Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
@@ -208,6 +258,12 @@ public class BrightLineFalsingManager implements FalsingManager {
if (reason != null) {
logInfo(reason);
}
} else {
// A valid double tap prevents an invalid single tap from going into history.
if (mSingleTapHistoryCanceller != null) {
mSingleTapHistoryCanceller.run();
mSingleTapHistoryCanceller = null;
}
}
return result.isFalse();
}

View File

@@ -115,6 +115,9 @@ public interface FalsingCollector {
/** */
void onTouchEvent(MotionEvent ev);
/** */
void avoidGesture();
/** */
void cleanup();
}

View File

@@ -146,6 +146,10 @@ public class FalsingCollectorFake implements FalsingCollector {
public void onTouchEvent(MotionEvent ev) {
}
@Override
public void avoidGesture() {
}
@Override
public void cleanup() {
}

View File

@@ -49,6 +49,8 @@ class FalsingCollectorImpl implements FalsingCollector {
private boolean mShowingAod;
private boolean mScreenOn;
private boolean mSessionStarted;
private MotionEvent mPendingDownEvent;
private boolean mAvoidGesture;
private final ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent;
@@ -245,7 +247,32 @@ class FalsingCollectorImpl implements FalsingCollector {
@Override
public void onTouchEvent(MotionEvent ev) {
mFalsingDataProvider.onMotionEvent(ev);
// We delay processing down events to see if another component wants to process them.
// If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
// will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
// avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
// any other events are processed, otherwise the whole gesture will be recorded.
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Make a copy of ev, since it will be recycled after we exit this method.
mPendingDownEvent = MotionEvent.obtain(ev);
mAvoidGesture = false;
} else if (!mAvoidGesture) {
if (mPendingDownEvent != null) {
mFalsingDataProvider.onMotionEvent(mPendingDownEvent);
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
mFalsingDataProvider.onMotionEvent(ev);
}
}
@Override
public void avoidGesture() {
if (mPendingDownEvent != null) {
mAvoidGesture = true;
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
}
@Override

View File

@@ -90,10 +90,8 @@ public class FalsingDataProvider {
}
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (!mRecentMotionEvents.isEmpty()) {
mExtendedMotionEvents.addFirst(mRecentMotionEvents);
mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
completePriorGesture();
mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
mRecentMotionEvents.addAll(motionEvents);
@@ -101,9 +99,25 @@ public class FalsingDataProvider {
mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
// We explicitly do not complete a gesture on UP or CANCEL events.
// We wait for the next gesture to start before marking the prior gesture as complete. This
// has multiple benefits. First, it makes it trivial to track the "current" or "recent"
// gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly,
// it ensures that the current gesture doesn't get added to this HistoryTracker before it
// is analyzed.
mDirty = true;
}
private void completePriorGesture() {
if (!mRecentMotionEvents.isEmpty()) {
mGestuerCompleteListeners.forEach(listener -> listener.onGestureComplete(
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mExtendedMotionEvents.addFirst(mRecentMotionEvents);
}
}
/** Returns screen width in pixels. */
public int getWidthPixels() {
return mWidthPixels;
@@ -146,13 +160,6 @@ public class FalsingDataProvider {
}
}
/**
* Returns true if new data has been supplied since the last time this class has been accessed.
*/
public boolean isDirty() {
return mDirty;
}
/** Return the interaction type that is being compared against for falsing. */
public final int getInteractionType() {
return mInteractionType;
@@ -387,6 +394,6 @@ public class FalsingDataProvider {
/** Callback to be alerted when the current gesture ends. */
public interface GestureCompleteListener {
/** */
void onGestureComplete();
void onGestureComplete(long completionTimeMs);
}
}

View File

@@ -31,6 +31,8 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import org.junit.Before;
import org.junit.Test;
@@ -65,6 +67,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
private LatencyTracker mLatencyTracker;
@Mock
private LiftToActivateListener mLiftToactivateListener;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
private View mDeleteButton;
@Mock
@@ -88,7 +91,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
.thenReturn(mOkButton);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) {
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mFalsingCollector) {
@Override
public void onResume(int reason) {
super.onResume(reason);

View File

@@ -16,12 +16,15 @@
package com.android.systemui.classifier;
import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.any;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +38,7 @@ import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -45,7 +49,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,6 +56,8 @@ import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class BrightLineClassifierTest extends SysuiTestCase {
private static final long DOUBLE_TAP_TIMEOUT_MS = 1000;
private BrightLineFalsingManager mBrightLineFalsingManager;
@Mock
private FalsingDataProvider mFalsingDataProvider;
@@ -69,24 +74,35 @@ public class BrightLineClassifierTest extends SysuiTestCase {
private FalsingClassifier mClassifierB;
private final List<MotionEvent> mMotionEventList = new ArrayList<>();
@Mock
private HistoryTracker mHistoryTracker;
private FakeSystemClock mSystemClock = new FakeSystemClock();
private HistoryTracker mHistoryTracker;;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
private GestureCompleteListener mGestureCompleteListener;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
when(mClassifierB.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
when(mSingleTapClassfier.isTap(any(List.class))).thenReturn(mPassedResult);
when(mDoubleTapClassifier.classifyGesture()).thenReturn(mPassedResult);
mClassifiers.add(mClassifierA);
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.isDirty()).thenReturn(true);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
mHistoryTracker, mSystemClock, false);
mHistoryTracker, mFakeExecutor, DOUBLE_TAP_TIMEOUT_MS, false);
ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
ArgumentCaptor.forClass(GestureCompleteListener.class);
verify(mFalsingDataProvider).addGestureCompleteListener(
gestureCompleteListenerCaptor.capture());
mGestureCompleteListener = gestureCompleteListenerCaptor.getValue();
}
@Test
@@ -179,15 +195,57 @@ public class BrightLineClassifierTest extends SysuiTestCase {
@Test
public void testHistory() {
ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
ArgumentCaptor.forClass(GestureCompleteListener.class);
mGestureCompleteListener.onGestureComplete(1000);
verify(mFalsingDataProvider).addGestureCompleteListener(
gestureCompleteListenerCaptor.capture());
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
GestureCompleteListener gestureCompleteListener = gestureCompleteListenerCaptor.getValue();
gestureCompleteListener.onGestureComplete();
@Test
public void testHistory_singleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false);
mGestureCompleteListener.onGestureComplete(1000);
verify(mHistoryTracker).addResults(any(Collection.class), eq(mSystemClock.uptimeMillis()));
verify(mHistoryTracker, never()).addResults(any(), anyLong());
mFakeExecutor.advanceClockToNext();
mFakeExecutor.runAllReady();
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@Test
public void testHistory_multipleSingleTaps() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false);
mGestureCompleteListener.onGestureComplete(1000);
mBrightLineFalsingManager.isFalseTap(false);
mGestureCompleteListener.onGestureComplete(2000);
verify(mHistoryTracker, never()).addResults(any(), anyLong());
mFakeExecutor.advanceClockToNext();
mFakeExecutor.runNextReady();
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
reset(mHistoryTracker);
mFakeExecutor.advanceClockToNext();
mFakeExecutor.runNextReady();
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
}
@Test
public void testHistory_doubleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false);
mGestureCompleteListener.onGestureComplete(1000);
// Before checking for double tap, we may check for single-tap on the second gesture.
mBrightLineFalsingManager.isFalseTap(false);
mBrightLineFalsingManager.isFalseDoubleTap();
mGestureCompleteListener.onGestureComplete(2000);
// Double tap is immediately added to history. Single tap is never added.
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
assertThat(mFakeExecutor.numPending()).isEqualTo(0);
}
}

View File

@@ -17,12 +17,15 @@
package com.android.systemui.classifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -38,6 +41,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -66,7 +70,6 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
mKeyguardUpdateMonitor, mProximitySensor, mStatusBarStateController);
}
@Test
public void testRegisterSensor() {
mFalsingCollector.onScreenTurningOn();
@@ -110,7 +113,6 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
@Test
public void testUnregisterSensor_StateTransition() {
ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
@@ -120,4 +122,37 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
}
@Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
// Nothing passed initially
mFalsingCollector.onTouchEvent(down);
verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
// Up event flushes the down event.
mFalsingCollector.onTouchEvent(up);
InOrder orderedCalls = inOrder(mFalsingDataProvider);
// We can't simply use "eq" or similar because the collector makes a copy of "down".
orderedCalls.verify(mFalsingDataProvider).onMotionEvent(
argThat(argument -> argument.getActionMasked() == MotionEvent.ACTION_DOWN));
orderedCalls.verify(mFalsingDataProvider).onMotionEvent(up);
}
@Test
public void testAvoidGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
// Nothing passed initially
mFalsingCollector.onTouchEvent(down);
verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
mFalsingCollector.avoidGesture();
// Up event would flush, but we were told to avoid.
mFalsingCollector.onTouchEvent(up);
verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
}
}