Merge "Disable face haptics during coex non-bypass by default" into sc-dev am: bf637195e9

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

Change-Id: I320df51da6978ac7f386c019e566572c504ad893
This commit is contained in:
Kevin Chyn
2021-07-30 05:35:22 +00:00
committed by Automerger Merge Worker
6 changed files with 125 additions and 15 deletions

View File

@@ -1106,6 +1106,11 @@ public class BiometricService extends SystemService {
return Settings.Secure.getInt(context.getContentResolver(), return Settings.Secure.getInt(context.getContentResolver(),
CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0; CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0;
} }
public boolean isCoexFaceNonBypassHapticsDisabled(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
} }
/** /**
@@ -1137,6 +1142,8 @@ public class BiometricService extends SystemService {
// by default. // by default.
CoexCoordinator coexCoordinator = CoexCoordinator.getInstance(); CoexCoordinator coexCoordinator = CoexCoordinator.getInstance();
coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context)); coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context));
coexCoordinator.setFaceHapticDisabledWhenNonBypass(
injector.isCoexFaceNonBypassHapticsDisabled(context));
try { try {
injector.getActivityManagerService().registerUserSwitchObserver( injector.getActivityManagerService().registerUserSwitchObserver(

View File

@@ -54,12 +54,18 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
public static final int STATE_NEW = 0; public static final int STATE_NEW = 0;
// Framework/HAL have started this operation // Framework/HAL have started this operation
public static final int STATE_STARTED = 1; public static final int STATE_STARTED = 1;
// Operation is started, but requires some user action (such as finger lift & re-touch) // Operation is started, but requires some user action to start (such as finger lift & re-touch)
public static final int STATE_STARTED_PAUSED = 2; public static final int STATE_STARTED_PAUSED = 2;
// Same as above, except auth was attempted (rejected, timed out, etc).
public static final int STATE_STARTED_PAUSED_ATTEMPTED = 3;
// Done, errored, canceled, etc. HAL/framework are not running this sensor anymore. // Done, errored, canceled, etc. HAL/framework are not running this sensor anymore.
public static final int STATE_STOPPED = 3; public static final int STATE_STOPPED = 4;
@IntDef({STATE_NEW, STATE_STARTED, STATE_STARTED_PAUSED, STATE_STOPPED}) @IntDef({STATE_NEW,
STATE_STARTED,
STATE_STARTED_PAUSED,
STATE_STARTED_PAUSED_ATTEMPTED,
STATE_STOPPED})
@interface State {} @interface State {}
private final boolean mIsStrongBiometric; private final boolean mIsStrongBiometric;

View File

@@ -45,6 +45,8 @@ public class CoexCoordinator {
private static final String TAG = "BiometricCoexCoordinator"; private static final String TAG = "BiometricCoexCoordinator";
public static final String SETTING_ENABLE_NAME = public static final String SETTING_ENABLE_NAME =
"com.android.server.biometrics.sensors.CoexCoordinator.enable"; "com.android.server.biometrics.sensors.CoexCoordinator.enable";
public static final String FACE_HAPTIC_DISABLE =
"com.android.server.biometrics.sensors.CoexCoordinator.disable_face_haptics";
private static final boolean DEBUG = true; private static final boolean DEBUG = true;
// Successful authentications should be used within this amount of time. // Successful authentications should be used within this amount of time.
@@ -144,6 +146,10 @@ public class CoexCoordinator {
mAdvancedLogicEnabled = enabled; mAdvancedLogicEnabled = enabled;
} }
public void setFaceHapticDisabledWhenNonBypass(boolean disabled) {
mFaceHapticDisabledWhenNonBypass = disabled;
}
@VisibleForTesting @VisibleForTesting
void reset() { void reset() {
mClientMap.clear(); mClientMap.clear();
@@ -153,6 +159,7 @@ public class CoexCoordinator {
private final Map<Integer, AuthenticationClient<?>> mClientMap; private final Map<Integer, AuthenticationClient<?>> mClientMap;
@VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths; @VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
private boolean mAdvancedLogicEnabled; private boolean mAdvancedLogicEnabled;
private boolean mFaceHapticDisabledWhenNonBypass;
private final Handler mHandler; private final Handler mHandler;
private CoexCoordinator() { private CoexCoordinator() {
@@ -225,7 +232,11 @@ public class CoexCoordinator {
mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths, mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths,
currentTimeMillis, SENSOR_TYPE_FACE, client, callback)); currentTimeMillis, SENSOR_TYPE_FACE, client, callback));
} else { } else {
callback.sendHapticFeedback(); if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
Slog.w(TAG, "Skipping face success haptic");
} else {
callback.sendHapticFeedback();
}
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth(); callback.handleLifecycleAfterAuth();
} }
@@ -278,12 +289,23 @@ public class CoexCoordinator {
// BiometricScheduler do not get stuck. // BiometricScheduler do not get stuck.
Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps); Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
callback.handleLifecycleAfterAuth(); callback.handleLifecycleAfterAuth();
} else { } else if (isUdfpsAuthAttempted(udfps)) {
// UDFPS is not actively authenticating (finger not touching, already // If UDFPS is STATE_STARTED_PAUSED (e.g. finger rejected but can still
// rejected, etc). // auth after pointer goes down, it means UDFPS encountered a rejection. In
// this case, we need to play the final reject haptic since face auth is
// also done now.
callback.sendHapticFeedback(); callback.sendHapticFeedback();
callback.handleLifecycleAfterAuth(); callback.handleLifecycleAfterAuth();
} }
else {
// UDFPS auth has never been attempted.
if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
Slog.w(TAG, "Skipping face reject haptic");
} else {
callback.sendHapticFeedback();
}
callback.handleLifecycleAfterAuth();
}
} else if (isCurrentUdfps(client)) { } else if (isCurrentUdfps(client)) {
// Face should either be running, or have already finished // Face should either be running, or have already finished
SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis); SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis);
@@ -374,6 +396,13 @@ public class CoexCoordinator {
return false; return false;
} }
private static boolean isUdfpsAuthAttempted(@Nullable AuthenticationClient<?> client) {
if (client instanceof Udfps) {
return client.getState() == AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED;
}
return false;
}
private boolean isUnknownClient(@NonNull AuthenticationClient<?> client) { private boolean isUnknownClient(@NonNull AuthenticationClient<?> client) {
for (AuthenticationClient<?> c : mClientMap.values()) { for (AuthenticationClient<?> c : mClientMap.values()) {
if (c == client) { if (c == client) {
@@ -400,6 +429,7 @@ public class CoexCoordinator {
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Enabled: ").append(mAdvancedLogicEnabled); sb.append("Enabled: ").append(mAdvancedLogicEnabled);
sb.append(", Face Haptic Disabled: ").append(mFaceHapticDisabledWhenNonBypass);
sb.append(", Queue size: " ).append(mSuccessfulAuths.size()); sb.append(", Queue size: " ).append(mSuccessfulAuths.size());
for (SuccessfulAuth auth : mSuccessfulAuths) { for (SuccessfulAuth auth : mSuccessfulAuths) {
sb.append(", Auth: ").append(auth.toString()); sb.append(", Auth: ").append(auth.toString());

View File

@@ -114,7 +114,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
mState = STATE_STOPPED; mState = STATE_STOPPED;
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
} else { } else {
mState = STATE_STARTED_PAUSED; mState = STATE_STARTED_PAUSED_ATTEMPTED;
} }
} }
@@ -188,7 +188,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
public void onPointerUp() { public void onPointerUp() {
try { try {
mIsPointerDown = false; mIsPointerDown = false;
mState = STATE_STARTED_PAUSED; mState = STATE_STARTED_PAUSED_ATTEMPTED;
mALSProbeCallback.getProbe().disable(); mALSProbeCallback.getProbe().disable();
getFreshDaemon().onPointerUp(0 /* pointerId */); getFreshDaemon().onPointerUp(0 /* pointerId */);

View File

@@ -112,7 +112,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
resetFailedAttempts(getTargetUserId()); resetFailedAttempts(getTargetUserId());
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
} else { } else {
mState = STATE_STARTED_PAUSED; mState = STATE_STARTED_PAUSED_ATTEMPTED;
final @LockoutTracker.LockoutMode int lockoutMode = final @LockoutTracker.LockoutMode int lockoutMode =
mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId()); mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
@@ -206,7 +206,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@Override @Override
public void onPointerUp() { public void onPointerUp() {
mIsPointerDown = false; mIsPointerDown = false;
mState = STATE_STARTED_PAUSED; mState = STATE_STARTED_PAUSED_ATTEMPTED;
mALSProbeCallback.getProbe().disable(); mALSProbeCallback.getProbe().disable();
UdfpsHelper.onFingerUp(getFreshDaemon()); UdfpsHelper.onFingerUp(getFreshDaemon());

View File

@@ -70,6 +70,7 @@ public class CoexCoordinatorTest {
mCoexCoordinator = CoexCoordinator.getInstance(); mCoexCoordinator = CoexCoordinator.getInstance();
mCoexCoordinator.setAdvancedLogicEnabled(true); mCoexCoordinator.setAdvancedLogicEnabled(true);
mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
} }
@Test @Test
@@ -151,11 +152,75 @@ public class CoexCoordinatorTest {
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
mCallback); mCallback);
verify(mCallback).sendHapticFeedback(); // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead
// of removed) to keep this context.
// verify(mCallback).sendHapticFeedback();
verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth(); verify(mCallback).handleLifecycleAfterAuth();
} }
@Test
public void testKeyguard_faceAuthSuccess_nonBypass_udfpsRunning_noHaptics() {
testKeyguard_bypass_haptics(false /* bypassEnabled */,
true /* faceAccepted */,
false /* shouldReceiveHaptics */);
}
@Test
public void testKeyguard_faceAuthReject_nonBypass_udfpsRunning_noHaptics() {
testKeyguard_bypass_haptics(false /* bypassEnabled */,
false /* faceAccepted */,
false /* shouldReceiveHaptics */);
}
@Test
public void testKeyguard_faceAuthSuccess_bypass_udfpsRunning_haptics() {
testKeyguard_bypass_haptics(true /* bypassEnabled */,
true /* faceAccepted */,
true /* shouldReceiveHaptics */);
}
@Test
public void testKeyguard_faceAuthReject_bypass_udfpsRunning_haptics() {
testKeyguard_bypass_haptics(true /* bypassEnabled */,
false /* faceAccepted */,
true /* shouldReceiveHaptics */);
}
private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted,
boolean shouldReceiveHaptics) {
mCoexCoordinator.reset();
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
when(faceClient.isKeyguard()).thenReturn(true);
when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
withSettings().extraInterfaces(Udfps.class));
when(udfpsClient.isKeyguard()).thenReturn(true);
when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
if (faceAccepted) {
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
mCallback);
} else {
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
}
if (shouldReceiveHaptics) {
verify(mCallback).sendHapticFeedback();
} else {
verify(mCallback, never()).sendHapticFeedback();
}
verify(mCallback).sendAuthenticationResult(eq(faceAccepted) /* addAuthTokenIfStrong */);
verify(mCallback).handleLifecycleAfterAuth();
}
@Test @Test
public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() { public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() {
testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */, testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */,
@@ -294,12 +359,13 @@ public class CoexCoordinatorTest {
} }
@Test @Test
public void testKeyguard_udfpsRejected_thenFaceRejected() { public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
mCoexCoordinator.reset(); mCoexCoordinator.reset();
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class); AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
when(faceClient.isKeyguard()).thenReturn(true); when(faceClient.isKeyguard()).thenReturn(true);
when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class, AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
withSettings().extraInterfaces(Udfps.class)); withSettings().extraInterfaces(Udfps.class));
@@ -312,8 +378,9 @@ public class CoexCoordinatorTest {
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient, mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
LockoutTracker.LOCKOUT_NONE, mCallback); LockoutTracker.LOCKOUT_NONE, mCallback);
// Client becomes paused, but finger does not necessarily lift, since we suppress the haptic // Auth was attempted
when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED_PAUSED); when(udfpsClient.getState())
.thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
verify(mCallback, never()).sendHapticFeedback(); verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth(); verify(mCallback).handleLifecycleAfterAuth();