Merge "3/n: Add CoexCoordinator" into sc-dev
This commit is contained in:
@@ -138,7 +138,6 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
|
|
||||||
final ClientMonitorCallbackConverter listener = getListener();
|
final ClientMonitorCallbackConverter listener = getListener();
|
||||||
|
|
||||||
try {
|
|
||||||
if (DEBUG) Slog.v(TAG, "onAuthenticated(" + authenticated + ")"
|
if (DEBUG) Slog.v(TAG, "onAuthenticated(" + authenticated + ")"
|
||||||
+ ", ID:" + identifier.getBiometricId()
|
+ ", ID:" + identifier.getBiometricId()
|
||||||
+ ", Owner: " + getOwnerString()
|
+ ", Owner: " + getOwnerString()
|
||||||
@@ -206,10 +205,6 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
|
|
||||||
mAlreadyDone = true;
|
mAlreadyDone = true;
|
||||||
|
|
||||||
if (listener != null && mShouldVibrate) {
|
|
||||||
vibrateSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mTaskStackListener != null) {
|
if (mTaskStackListener != null) {
|
||||||
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
|
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
|
||||||
}
|
}
|
||||||
@@ -225,54 +220,78 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
|
|||||||
getSensorId(), getTargetUserId(), byteToken);
|
getSensorId(), getTargetUserId(), byteToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBiometricPrompt() && listener != null) {
|
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
||||||
// BiometricService will add the token to keystore
|
coordinator.onAuthenticationSucceeded(this, new CoexCoordinator.Callback() {
|
||||||
listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
|
@Override
|
||||||
getTargetUserId(), mIsStrongBiometric);
|
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
||||||
} else if (!isBiometricPrompt() && listener != null) {
|
if (addAuthTokenIfStrong && mIsStrongBiometric) {
|
||||||
if (mIsStrongBiometric) {
|
|
||||||
final int result = KeyStore.getInstance().addAuthToken(byteToken);
|
final int result = KeyStore.getInstance().addAuthToken(byteToken);
|
||||||
Slog.d(TAG, "addAuthToken: " + result);
|
Slog.d(TAG, "addAuthToken: " + result);
|
||||||
} else {
|
} else {
|
||||||
Slog.d(TAG, "Skipping addAuthToken");
|
Slog.d(TAG, "Skipping addAuthToken");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly have if/else here to make it super obvious in case the code is
|
if (listener != null) {
|
||||||
// touched in the future.
|
try {
|
||||||
|
// Explicitly have if/else here to make it super obvious in case the
|
||||||
|
// code is touched in the future.
|
||||||
if (!mIsRestricted) {
|
if (!mIsRestricted) {
|
||||||
listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
|
listener.onAuthenticationSucceeded(getSensorId(),
|
||||||
getTargetUserId(), mIsStrongBiometric);
|
identifier,
|
||||||
|
byteToken,
|
||||||
|
getTargetUserId(),
|
||||||
|
mIsStrongBiometric);
|
||||||
} else {
|
} else {
|
||||||
listener.onAuthenticationSucceeded(getSensorId(), null /* identifier */,
|
listener.onAuthenticationSucceeded(getSensorId(),
|
||||||
byteToken, getTargetUserId(), mIsStrongBiometric);
|
null /* identifier */,
|
||||||
|
byteToken,
|
||||||
|
getTargetUserId(),
|
||||||
|
mIsStrongBiometric);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Unable to notify listener", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Client not listening
|
|
||||||
Slog.w(TAG, "Client not listening");
|
Slog.w(TAG, "Client not listening");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (listener != null && mShouldVibrate) {
|
|
||||||
vibrateError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendHapticFeedback() {
|
||||||
|
if (listener != null && mShouldVibrate) {
|
||||||
|
vibrateSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
// Allow system-defined limit of number of attempts before giving up
|
// Allow system-defined limit of number of attempts before giving up
|
||||||
final @LockoutTracker.LockoutMode int lockoutMode =
|
final @LockoutTracker.LockoutMode int lockoutMode =
|
||||||
handleFailedAttempt(getTargetUserId());
|
handleFailedAttempt(getTargetUserId());
|
||||||
if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
|
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
|
||||||
// Don't send onAuthenticationFailed if we're in lockout, it causes a
|
|
||||||
// janky UI on Keyguard/BiometricPrompt since "authentication failed"
|
|
||||||
// will show briefly and be replaced by "device locked out" message.
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onAuthenticationFailed(getSensorId());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mAlreadyDone = true;
|
mAlreadyDone = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
|
||||||
|
coordinator.onAuthenticationRejected(this, lockoutMode,
|
||||||
|
new CoexCoordinator.Callback() {
|
||||||
|
@Override
|
||||||
|
public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
|
||||||
|
if (listener != null) {
|
||||||
|
try {
|
||||||
|
listener.onAuthenticationFailed(getSensorId());
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.e(TAG, "Unable to notify listener, finishing", e);
|
Slog.e(TAG, "Unable to notify listener", e);
|
||||||
mCallback.onClientFinished(this, false /* success */);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendHapticFeedback() {
|
||||||
|
if (listener != null && mShouldVibrate) {
|
||||||
|
vibrateError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton that contains the core logic for determining if haptics and authentication callbacks
|
||||||
|
* should be sent to receivers. Note that this class is used even when coex is not required (e.g.
|
||||||
|
* single sensor devices, or multi-sensor devices where only a single sensor is authenticating).
|
||||||
|
* This allows us to have all business logic in one testable place.
|
||||||
|
*/
|
||||||
|
public class CoexCoordinator {
|
||||||
|
|
||||||
|
private static final String TAG = "BiometricCoexCoordinator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
|
||||||
|
* logic.
|
||||||
|
*/
|
||||||
|
interface Callback {
|
||||||
|
/**
|
||||||
|
* Requests the owner to send the result (success/reject) and any associated info to the
|
||||||
|
* receiver (e.g. keyguard, BiometricService, etc).
|
||||||
|
*/
|
||||||
|
void sendAuthenticationResult(boolean addAuthTokenIfStrong);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the owner to initiate a vibration for this event.
|
||||||
|
*/
|
||||||
|
void sendHapticFeedback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CoexCoordinator sInstance;
|
||||||
|
|
||||||
|
private CoexCoordinator() {
|
||||||
|
// Singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static CoexCoordinator getInstance() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new CoexCoordinator();
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAuthenticationSucceeded(@NonNull AuthenticationClient<?> client,
|
||||||
|
@NonNull Callback callback) {
|
||||||
|
if (client.isBiometricPrompt()) {
|
||||||
|
callback.sendHapticFeedback();
|
||||||
|
// For BP, BiometricService will add the authToken to Keystore.
|
||||||
|
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
|
||||||
|
} else {
|
||||||
|
// Keyguard, FingerprintManager, FaceManager, etc
|
||||||
|
callback.sendHapticFeedback();
|
||||||
|
callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAuthenticationRejected(@NonNull AuthenticationClient<?> client,
|
||||||
|
@LockoutTracker.LockoutMode int lockoutMode,
|
||||||
|
@NonNull Callback callback) {
|
||||||
|
callback.sendHapticFeedback();
|
||||||
|
if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
|
||||||
|
// Don't send onAuthenticationFailed if we're in lockout, it causes a
|
||||||
|
// janky UI on Keyguard/BiometricPrompt since "authentication failed"
|
||||||
|
// will show briefly and be replaced by "device locked out" message.
|
||||||
|
callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
@Presubmit
|
||||||
|
@SmallTest
|
||||||
|
public class CoexCoordinatorTest {
|
||||||
|
|
||||||
|
private CoexCoordinator mCoexCoordinator;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CoexCoordinator.Callback mCallback;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mCoexCoordinator = CoexCoordinator.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBiometricPrompt_authSuccess() {
|
||||||
|
AuthenticationClient<?> client = mock(AuthenticationClient.class);
|
||||||
|
when(client.isBiometricPrompt()).thenReturn(true);
|
||||||
|
|
||||||
|
mCoexCoordinator.onAuthenticationSucceeded(client, mCallback);
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBiometricPrompt_authReject_whenNotLockedOut() {
|
||||||
|
AuthenticationClient<?> client = mock(AuthenticationClient.class);
|
||||||
|
when(client.isBiometricPrompt()).thenReturn(true);
|
||||||
|
|
||||||
|
mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_NONE, mCallback);
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBiometricPrompt_authReject_whenLockedOut() {
|
||||||
|
AuthenticationClient<?> client = mock(AuthenticationClient.class);
|
||||||
|
when(client.isBiometricPrompt()).thenReturn(true);
|
||||||
|
|
||||||
|
mCoexCoordinator.onAuthenticationRejected(client, LockoutTracker.LOCKOUT_TIMED, mCallback);
|
||||||
|
verify(mCallback).sendHapticFeedback();
|
||||||
|
verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user