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

This commit is contained in:
Kevin Chyn
2021-07-30 05:20:32 +00:00
committed by Android (Google) Code Review
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(),
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.
CoexCoordinator coexCoordinator = CoexCoordinator.getInstance();
coexCoordinator.setAdvancedLogicEnabled(injector.isAdvancedCoexLogicEnabled(context));
coexCoordinator.setFaceHapticDisabledWhenNonBypass(
injector.isCoexFaceNonBypassHapticsDisabled(context));
try {
injector.getActivityManagerService().registerUserSwitchObserver(

View File

@@ -54,12 +54,18 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
public static final int STATE_NEW = 0;
// Framework/HAL have started this operation
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;
// 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.
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 {}
private final boolean mIsStrongBiometric;

View File

@@ -45,6 +45,8 @@ public class CoexCoordinator {
private static final String TAG = "BiometricCoexCoordinator";
public static final String SETTING_ENABLE_NAME =
"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;
// Successful authentications should be used within this amount of time.
@@ -144,6 +146,10 @@ public class CoexCoordinator {
mAdvancedLogicEnabled = enabled;
}
public void setFaceHapticDisabledWhenNonBypass(boolean disabled) {
mFaceHapticDisabledWhenNonBypass = disabled;
}
@VisibleForTesting
void reset() {
mClientMap.clear();
@@ -153,6 +159,7 @@ public class CoexCoordinator {
private final Map<Integer, AuthenticationClient<?>> mClientMap;
@VisibleForTesting final LinkedList<SuccessfulAuth> mSuccessfulAuths;
private boolean mAdvancedLogicEnabled;
private boolean mFaceHapticDisabledWhenNonBypass;
private final Handler mHandler;
private CoexCoordinator() {
@@ -225,7 +232,11 @@ public class CoexCoordinator {
mSuccessfulAuths.add(new SuccessfulAuth(mHandler, mSuccessfulAuths,
currentTimeMillis, SENSOR_TYPE_FACE, client, callback));
} else {
callback.sendHapticFeedback();
if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) {
Slog.w(TAG, "Skipping face success haptic");
} else {
callback.sendHapticFeedback();
}
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
callback.handleLifecycleAfterAuth();
}
@@ -278,12 +289,23 @@ public class CoexCoordinator {
// BiometricScheduler do not get stuck.
Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
callback.handleLifecycleAfterAuth();
} else {
// UDFPS is not actively authenticating (finger not touching, already
// rejected, etc).
} else if (isUdfpsAuthAttempted(udfps)) {
// If UDFPS is STATE_STARTED_PAUSED (e.g. finger rejected but can still
// 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.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)) {
// Face should either be running, or have already finished
SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis);
@@ -374,6 +396,13 @@ public class CoexCoordinator {
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) {
for (AuthenticationClient<?> c : mClientMap.values()) {
if (c == client) {
@@ -400,6 +429,7 @@ public class CoexCoordinator {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Enabled: ").append(mAdvancedLogicEnabled);
sb.append(", Face Haptic Disabled: ").append(mFaceHapticDisabledWhenNonBypass);
sb.append(", Queue size: " ).append(mSuccessfulAuths.size());
for (SuccessfulAuth auth : mSuccessfulAuths) {
sb.append(", Auth: ").append(auth.toString());

View File

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

View File

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

View File

@@ -70,6 +70,7 @@ public class CoexCoordinatorTest {
mCoexCoordinator = CoexCoordinator.getInstance();
mCoexCoordinator.setAdvancedLogicEnabled(true);
mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true);
}
@Test
@@ -151,11 +152,75 @@ public class CoexCoordinatorTest {
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
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).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
public void testKeyguard_faceAuth_udfpsTouching_faceSuccess_thenUdfpsRejectedWithinBounds() {
testKeyguard_faceAuth_udfpsTouching_faceSuccess(false /* thenUdfpsAccepted */,
@@ -294,12 +359,13 @@ public class CoexCoordinatorTest {
}
@Test
public void testKeyguard_udfpsRejected_thenFaceRejected() {
public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() {
mCoexCoordinator.reset();
AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
when(faceClient.isKeyguard()).thenReturn(true);
when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case
AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
withSettings().extraInterfaces(Udfps.class));
@@ -312,8 +378,9 @@ public class CoexCoordinatorTest {
mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
LockoutTracker.LOCKOUT_NONE, mCallback);
// Client becomes paused, but finger does not necessarily lift, since we suppress the haptic
when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED_PAUSED);
// Auth was attempted
when(udfpsClient.getState())
.thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED);
verify(mCallback, never()).sendHapticFeedback();
verify(mCallback).handleLifecycleAfterAuth();