Merge "Disable face haptics during coex non-bypass by default" into sc-dev
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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 */);
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user