Merge changes from topic "biometric-service-migration"

* changes:
  Remove strings from low level onError(...) calls
  Prepare BiometricService for migration to a module
This commit is contained in:
Ilya Matyukhin
2019-10-21 21:22:43 +00:00
committed by Android (Google) Code Review
26 changed files with 827 additions and 472 deletions

View File

@@ -36,23 +36,30 @@ public interface BiometricAuthenticator {
* @hide
*/
int TYPE_NONE = 0;
/**
* Constant representing credential (PIN, pattern, or password).
* @hide
*/
int TYPE_CREDENTIAL = 1 << 0;
/**
* Constant representing fingerprint.
* @hide
*/
int TYPE_FINGERPRINT = 1 << 0;
int TYPE_FINGERPRINT = 1 << 1;
/**
* Constant representing iris.
* @hide
*/
int TYPE_IRIS = 1 << 1;
int TYPE_IRIS = 1 << 2;
/**
* Constant representing face.
* @hide
*/
int TYPE_FACE = 1 << 2;
int TYPE_FACE = 1 << 3;
/**
* Container for biometric data

View File

@@ -133,6 +133,13 @@ public interface BiometricConstants {
*/
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
*/
int BIOMETRIC_PAUSED_REJECTED = 100;
/**
* @hide
*/

View File

@@ -137,7 +137,7 @@ public class BiometricManager {
public boolean hasEnrolledBiometrics(int userId) {
if (mService != null) {
try {
return mService.hasEnrolledBiometrics(userId);
return mService.hasEnrolledBiometrics(userId, mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in hasEnrolledBiometrics(): " + e);
return false;

View File

@@ -26,6 +26,8 @@ import android.annotation.RequiresPermission;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -339,9 +341,23 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
@Override
public void onError(int error, String message) throws RemoteException {
public void onError(int modality, int error, int vendorCode) throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationError(error, message);
String errorMessage;
switch (modality) {
case TYPE_FACE:
errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
break;
case TYPE_FINGERPRINT:
errorMessage = FingerprintManager.getErrorString(mContext, error,
vendorCode);
break;
default:
errorMessage = "";
}
mAuthenticationCallback.onAuthenticationError(error, errorMessage);
});
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2019 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 android.hardware.biometrics;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
/**
* This interface encapsulates fingerprint, face, iris, etc. authenticators.
* Implementations of this interface are meant to be registered with BiometricService.
* @hide
*/
interface IBiometricAuthenticator {
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
// Same as above, with extra arguments.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(String opPackageName);
// Determine if a user has at least one enrolled face
boolean hasEnrolledTemplates(int userId, String opPackageName);
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetLockout(in byte [] token);
// Explicitly set the active user (for enrolling work profile)
void setActiveUser(int uid);
}

View File

@@ -19,6 +19,7 @@ package android.hardware.biometrics;
import android.os.Bundle;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricAuthenticator;
/**
* Communication channel from BiometricPrompt and BiometricManager to BiometricService. The
@@ -40,7 +41,12 @@ interface IBiometricService {
int canAuthenticate(String opPackageName, int userId);
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId);
boolean hasEnrolledBiometrics(int userId, String opPackageName);
// Registers an authenticator (e.g. face, fingerprint, iris).
// Id must be unique, whereas strength and modality don't need to be.
// TODO(b/123321528): Turn strength and modality into enums.
void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);

View File

@@ -25,7 +25,7 @@ oneway interface IBiometricServiceReceiver {
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
void onError(int error, String message);
void onError(int modality, int error, int vendorCode);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.

View File

@@ -31,7 +31,7 @@ oneway interface IBiometricServiceReceiverInternal {
void onAuthenticationFailed();
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
void onError(int cookie, int error, String message);
void onError(int cookie, int modality, int error, int vendorCode);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.

View File

@@ -504,8 +504,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public boolean isHardwareDetected() {
if (mService != null) {
try {
long deviceId = 0; /* TODO: plumb hardware id to FPMS */
return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
return mService.isHardwareDetected(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -67,7 +67,7 @@ interface IFaceService {
List<Face> getEnrolledFaces(int userId, String opPackageName);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(long deviceId, String opPackageName);
boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
long generateChallenge(IBinder token);

View File

@@ -691,8 +691,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
public boolean isHardwareDetected() {
if (mService != null) {
try {
long deviceId = 0; /* TODO: plumb hardware id to FPMS */
return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
return mService.isHardwareDetected(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -71,7 +71,7 @@ interface IFingerprintService {
List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(long deviceId, String opPackageName);
boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
long preEnroll(IBinder token);

View File

@@ -155,12 +155,12 @@ oneway interface IStatusBar
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
// Used to notify the authentication dialog that a biometric has been authenticated or rejected
void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(int errorCode, String error);
// Used to show an error - the dialog will dismiss after a certain amount of time
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();

View File

@@ -104,12 +104,12 @@ interface IStatusBarService
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
// Used to notify the authentication dialog that a biometric has been authenticated or rejected
void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
void onBiometricError(int errorCode, String error);
// Used to show an error - the dialog will dismiss after a certain amount of time
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
}

View File

@@ -16,6 +16,9 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -27,6 +30,8 @@ import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -34,6 +39,7 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.systemui.SystemUI;
@@ -229,15 +235,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
@Override
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
+ " reason: " + failureReason);
if (authenticated) {
mCurrentDialog.onAuthenticationSucceeded();
} else {
mCurrentDialog.onAuthenticationFailed(failureReason);
}
public void onBiometricAuthenticated() {
mCurrentDialog.onAuthenticationSucceeded();
}
@Override
@@ -247,16 +246,45 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mCurrentDialog.onHelp(message);
}
@Override
public void onBiometricError(int errorCode, String error) {
if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
private String getErrorString(int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
return FaceManager.getErrorString(mContext, error, vendorCode);
case TYPE_FINGERPRINT:
return FingerprintManager.getErrorString(mContext, error, vendorCode);
default:
return "";
}
}
@Override
public void onBiometricError(int modality, int error, int vendorCode) {
if (DEBUG) {
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
// TODO(b/141025588): Create separate methods for handling hard and soft errors.
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
|| error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
|| errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
mCurrentDialog.animateToCredentialUI();
} else if (isSoftError) {
final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
? mContext.getString(R.string.biometric_not_recognized)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
mCurrentDialog.onAuthenticationFailed(errorMessage);
} else {
mCurrentDialog.onError(error);
final String errorMessage = getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(errorMessage);
}
}

View File

@@ -261,9 +261,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAuthenticationDialog(Bundle bundle,
IBiometricServiceReceiverInternal receiver, int biometricModality,
boolean requireConfirmation, int userId, String opPackageName) { }
default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
default void onBiometricAuthenticated() { }
default void onBiometricHelp(String message) { }
default void onBiometricError(int errorCode, String error) { }
default void onBiometricError(int modality, int error, int vendorCode) { }
default void hideAuthenticationDialog() { }
/**
@@ -792,12 +792,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
public void onBiometricAuthenticated() {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = authenticated;
args.arg2 = failureReason;
mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
}
}
@@ -809,9 +806,13 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
public void onBiometricError(int errorCode, String error) {
public void onBiometricError(int modality, int error, int vendorCode) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget();
SomeArgs args = SomeArgs.obtain();
args.argi1 = modality;
args.argi2 = error;
args.argi3 = vendorCode;
mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, args).sendToTarget();
}
}
@@ -1098,13 +1099,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
break;
}
case MSG_BIOMETRIC_AUTHENTICATED: {
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricAuthenticated(
(boolean) someArgs.arg1 /* authenticated */,
(String) someArgs.arg2 /* failureReason */);
mCallbacks.get(i).onBiometricAuthenticated();
}
someArgs.recycle();
break;
}
case MSG_BIOMETRIC_HELP:
@@ -1113,9 +1110,15 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
break;
case MSG_BIOMETRIC_ERROR:
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj);
mCallbacks.get(i).onBiometricError(
someArgs.argi1 /* modality */,
someArgs.argi2 /* error */,
someArgs.argi3 /* vendorCode */
);
}
someArgs.recycle();
break;
case MSG_BIOMETRIC_HIDE:
for (int i = 0; i < mCallbacks.size(); i++) {

View File

@@ -38,15 +38,18 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -89,9 +92,9 @@ public class AuthControllerTest extends SysuiTestCase {
when(context.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
.thenReturn(true);
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
.thenReturn(true);
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
@@ -170,20 +173,34 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
mAuthController.onBiometricAuthenticated(true, null /* failureReason */);
mAuthController.onBiometricAuthenticated();
verify(mDialog1).onAuthenticationSucceeded();
}
@Test
public void testOnAuthenticationFailedInvoked_whenSystemRequested() {
public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final String failureReason = "failure reason";
mAuthController.onBiometricAuthenticated(false, failureReason);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
assertEquals(captor.getValue(), failureReason);
assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
}
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
@@ -199,27 +216,27 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Test
public void testOnErrorInvoked_whenSystemRequested() {
public void testOnErrorInvoked_whenSystemRequested() throws Exception {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = 1;
final String errMessage = "error message";
mAuthController.onBiometricError(error, errMessage);
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(captor.capture());
assertEquals(captor.getValue(), errMessage);
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final String errorString = "lockout";
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
mAuthController.onBiometricError(error, errorString);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@@ -228,11 +245,11 @@ public class AuthControllerTest extends SysuiTestCase {
public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final String errorString = "lockout_permanent";
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
mAuthController.onBiometricError(error, errorString);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@@ -241,12 +258,12 @@ public class AuthControllerTest extends SysuiTestCase {
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final String errorString = "lockout";
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
mAuthController.onBiometricError(error, errorString);
verify(mDialog1).onError(eq(errorString));
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}
@@ -254,12 +271,12 @@ public class AuthControllerTest extends SysuiTestCase {
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final String errorString = "lockout_permanent";
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
mAuthController.onBiometricError(error, errorString);
verify(mDialog1).onError(eq(errorString));
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}

View File

@@ -418,10 +418,9 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testOnBiometricAuthenticated() {
String failureReason = "test_failure_reason";
mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason);
mCommandQueue.onBiometricAuthenticated();
waitForIdleSync();
verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason));
verify(mCallbacks).onBiometricAuthenticated();
}
@Test
@@ -434,11 +433,12 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testOnBiometricError() {
final int errorCode = 1;
String errorMessage = "test_error_message";
mCommandQueue.onBiometricError(errorCode, errorMessage);
final int modality = 1;
final int error = 2;
final int vendorCode = 3;
mCommandQueue.onBiometricError(modality, error, vendorCode);
waitForIdleSync();
verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage));
verify(mCallbacks).onBiometricError(eq(modality), eq(error), eq(vendorCode));
}
@Test

View File

@@ -37,13 +37,12 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
import android.net.Uri;
import android.os.Binder;
@@ -68,6 +67,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
import com.android.server.biometrics.face.FaceAuthenticator;
import com.android.server.biometrics.fingerprint.FingerprintAuthenticator;
import java.util.ArrayList;
import java.util.HashMap;
@@ -95,11 +96,6 @@ public class BiometricService extends SystemService {
private static final int MSG_CANCEL_AUTHENTICATION = 10;
private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
private static final int[] FEATURE_ID = {
TYPE_FINGERPRINT,
TYPE_IRIS,
TYPE_FACE
};
/**
* Authentication either just called and we have not transitioned to the CALLED state, or
@@ -172,7 +168,7 @@ public class BiometricService extends SystemService {
byte[] mTokenEscrow;
// Waiting for SystemUI to complete animation
int mErrorEscrow;
String mErrorStringEscrow;
int mVendorCodeEscrow;
// Timestamp when authentication started
private long mStartTimeMs;
@@ -219,18 +215,14 @@ public class BiometricService extends SystemService {
private final Injector mInjector;
@VisibleForTesting
final IBiometricService.Stub mImpl;
private final boolean mHasFeatureFace;
private final boolean mHasFeatureFingerprint;
private final boolean mHasFeatureIris;
private final boolean mHasFeatureFace;
@VisibleForTesting
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
@VisibleForTesting
IFingerprintService mFingerprintService;
@VisibleForTesting
IFaceService mFaceService;
@VisibleForTesting
IStatusBarService mStatusBarService;
@VisibleForTesting
@@ -262,7 +254,7 @@ public class BiometricService extends SystemService {
}
case MSG_ON_AUTHENTICATION_REJECTED: {
handleAuthenticationRejected((String) msg.obj /* failureReason */);
handleAuthenticationRejected();
break;
}
@@ -270,8 +262,9 @@ public class BiometricService extends SystemService {
SomeArgs args = (SomeArgs) msg.obj;
handleOnError(
args.argi1 /* cookie */,
args.argi2 /* error */,
(String) args.arg1 /* message */);
args.argi2 /* modality */,
args.argi3 /* error */,
args.argi4 /* vendorCode */);
args.recycle();
break;
}
@@ -331,7 +324,12 @@ public class BiometricService extends SystemService {
}
case MSG_ON_AUTHENTICATION_TIMED_OUT: {
handleAuthenticationTimedOut((String) msg.obj /* errorMessage */);
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticationTimedOut(
args.argi1 /* modality */,
args.argi2 /* error */,
args.argi3 /* vendorCode */);
args.recycle();
break;
}
@@ -347,21 +345,23 @@ public class BiometricService extends SystemService {
}
};
private final class AuthenticatorWrapper {
final int mType;
final BiometricAuthenticator mAuthenticator;
/**
* Wraps IBiometricAuthenticator implementation and stores information about the authenticator.
* TODO(b/141025588): Consider refactoring the tests to not rely on this implementation detail.
*/
@VisibleForTesting
public static final class AuthenticatorWrapper {
public final int id;
public final int strength;
public final int modality;
public final IBiometricAuthenticator impl;
AuthenticatorWrapper(int type, BiometricAuthenticator authenticator) {
mType = type;
mAuthenticator = authenticator;
}
int getType() {
return mType;
}
BiometricAuthenticator getAuthenticator() {
return mAuthenticator;
AuthenticatorWrapper(int id, int strength, int modality,
IBiometricAuthenticator impl) {
this.id = id;
this.strength = strength;
this.modality = modality;
this.impl = impl;
}
}
@@ -521,23 +521,28 @@ public class BiometricService extends SystemService {
@Override
public void onAuthenticationFailed()
throws RemoteException {
String failureReason = getContext().getString(R.string.biometric_not_recognized);
Slog.v(TAG, "onAuthenticationFailed: " + failureReason);
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget();
Slog.v(TAG, "onAuthenticationFailed");
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
}
@Override
public void onError(int cookie, int error, String message) throws RemoteException {
public void onError(int cookie, int modality, int error, int vendorCode)
throws RemoteException {
// Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
// soft errors and we should allow the user to try authenticating again instead of
// dismissing BiometricPrompt.
if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget();
SomeArgs args = SomeArgs.obtain();
args.argi1 = modality;
args.argi2 = error;
args.argi3 = vendorCode;
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
} else {
SomeArgs args = SomeArgs.obtain();
args.argi1 = cookie;
args.argi2 = error;
args.arg1 = message;
args.argi2 = modality;
args.argi3 = error;
args.argi4 = vendorCode;
mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
}
}
@@ -666,7 +671,8 @@ public class BiometricService extends SystemService {
final long ident = Binder.clearCallingIdentity();
int error;
try {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
opPackageName);
error = result.second;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -675,22 +681,30 @@ public class BiometricService extends SystemService {
}
@Override
public boolean hasEnrolledBiometrics(int userId) {
public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
for (int i = 0; i < mAuthenticators.size(); i++) {
if (mAuthenticators.get(i).mAuthenticator.hasEnrolledTemplates(userId)) {
for (AuthenticatorWrapper authenticator : mAuthenticators) {
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
return true;
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
return false;
}
@Override
public void registerAuthenticator(int id, int strength, int modality,
IBiometricAuthenticator authenticator) {
mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
}
@Override // Binder call
public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
throws RemoteException {
@@ -710,9 +724,11 @@ public class BiometricService extends SystemService {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
for (int i = 0; i < mAuthenticators.size(); i++) {
mAuthenticators.get(i).getAuthenticator().setActiveUser(userId);
for (AuthenticatorWrapper authenticator : mAuthenticators) {
authenticator.impl.setActiveUser(userId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -723,11 +739,8 @@ public class BiometricService extends SystemService {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
if (mFingerprintService != null) {
mFingerprintService.resetTimeout(token);
}
if (mFaceService != null) {
mFaceService.resetLockout(token);
for (AuthenticatorWrapper authenticator : mAuthenticators) {
authenticator.impl.resetLockout(token);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -750,40 +763,69 @@ public class BiometricService extends SystemService {
}
}
/**
* Class for injecting dependencies into BiometricService.
* TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger).
*/
@VisibleForTesting
static class Injector {
IActivityManager getActivityManagerService() {
public static class Injector {
@VisibleForTesting
public IActivityManager getActivityManagerService() {
return ActivityManager.getService();
}
IStatusBarService getStatusBarService() {
@VisibleForTesting
public IStatusBarService getStatusBarService() {
return IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
IFingerprintService getFingerprintService() {
return IFingerprintService.Stub.asInterface(
ServiceManager.getService(Context.FINGERPRINT_SERVICE));
/**
* Allows to mock FaceAuthenticator for testing.
*/
@VisibleForTesting
public IBiometricAuthenticator getFingerprintAuthenticator() {
return new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
}
IFaceService getFaceService() {
return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE));
/**
* Allows to mock FaceAuthenticator for testing.
*/
@VisibleForTesting
public IBiometricAuthenticator getFaceAuthenticator() {
return new FaceAuthenticator(
IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
}
SettingObserver getSettingObserver(Context context, Handler handler,
/**
* Allows to mock SettingObserver for testing.
*/
@VisibleForTesting
public SettingObserver getSettingObserver(Context context, Handler handler,
List<EnabledOnKeyguardCallback> callbacks) {
return new SettingObserver(context, handler, callbacks);
}
KeyStore getKeyStore() {
@VisibleForTesting
public KeyStore getKeyStore() {
return KeyStore.getInstance();
}
boolean isDebugEnabled(Context context, int userId) {
/**
* Allows to enable/disable debug logs.
*/
@VisibleForTesting
public boolean isDebugEnabled(Context context, int userId) {
return Utils.isDebugEnabled(context, userId);
}
void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
/**
* Allows to stub publishBinderService(...) for testing.
*/
@VisibleForTesting
public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
}
}
@@ -812,9 +854,9 @@ public class BiometricService extends SystemService {
mEnabledOnKeyguardCallbacks);
final PackageManager pm = context.getPackageManager();
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -833,26 +875,30 @@ public class BiometricService extends SystemService {
@Override
public void onStart() {
// TODO: maybe get these on-demand
if (mHasFeatureFingerprint) {
mFingerprintService = mInjector.getFingerprintService();
}
if (mHasFeatureFace) {
mFaceService = mInjector.getFaceService();
// TODO(b/141025588): remove this code block once AuthService is integrated.
{
if (mHasFeatureFace) {
try {
mImpl.registerAuthenticator(0, 0, TYPE_FACE, mInjector.getFaceAuthenticator());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
if (mHasFeatureFingerprint) {
try {
mImpl.registerAuthenticator(0, 0, TYPE_FINGERPRINT,
mInjector.getFingerprintAuthenticator());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
if (mHasFeatureIris) {
Slog.e(TAG, "Iris is not supported");
}
}
mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
// Cache the authenticators
for (int featureId : FEATURE_ID) {
if (hasFeature(featureId)) {
AuthenticatorWrapper authenticator =
new AuthenticatorWrapper(featureId, getAuthenticator(featureId));
mAuthenticators.add(authenticator);
}
}
mInjector.publishBinderService(this, mImpl);
}
@@ -868,7 +914,7 @@ public class BiometricService extends SystemService {
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
*/
private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
// No biometric features, send error
if (mAuthenticators.isEmpty()) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
@@ -886,23 +932,26 @@ public class BiometricService extends SystemService {
int modality = TYPE_NONE;
int firstHwAvailable = TYPE_NONE;
for (AuthenticatorWrapper authenticatorWrapper : mAuthenticators) {
modality = authenticatorWrapper.getType();
BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
if (authenticator.isHardwareDetected()) {
isHardwareDetected = true;
if (firstHwAvailable == TYPE_NONE) {
// Store the first one since we want to return the error in correct priority
// order.
firstHwAvailable = modality;
}
if (authenticator.hasEnrolledTemplates(userId)) {
hasTemplatesEnrolled = true;
if (isEnabledForApp(modality, userId)) {
enabledForApps = true;
break;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
modality = authenticator.modality;
try {
if (authenticator.impl.isHardwareDetected(opPackageName)) {
isHardwareDetected = true;
if (firstHwAvailable == TYPE_NONE) {
// Store the first one since we want to return the error in correct priority
// order.
firstHwAvailable = modality;
}
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
hasTemplatesEnrolled = true;
if (isEnabledForApp(modality, userId)) {
enabledForApps = true;
break;
}
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@@ -926,7 +975,7 @@ public class BiometricService extends SystemService {
}
private boolean isEnabledForApp(int modality, int userId) {
switch(modality) {
switch (modality) {
case TYPE_FINGERPRINT:
return true;
case TYPE_IRIS:
@@ -939,47 +988,16 @@ public class BiometricService extends SystemService {
}
}
private String getErrorString(int type, int error, int vendorCode) {
switch (type) {
case TYPE_FINGERPRINT:
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
case TYPE_IRIS:
Slog.w(TAG, "Modality not supported");
return null; // not supported
case TYPE_FACE:
return FaceManager.getErrorString(getContext(), error, vendorCode);
default:
Slog.w(TAG, "Unable to get error string for modality: " + type);
return null;
}
}
private BiometricAuthenticator getAuthenticator(int type) {
switch (type) {
case TYPE_FINGERPRINT:
return (FingerprintManager)
getContext().getSystemService(Context.FINGERPRINT_SERVICE);
case TYPE_IRIS:
return null;
case TYPE_FACE:
return (FaceManager)
getContext().getSystemService(Context.FACE_SERVICE);
default:
return null;
}
}
private boolean hasFeature(int type) {
switch (type) {
case TYPE_FINGERPRINT:
return mHasFeatureFingerprint;
case TYPE_IRIS:
return mHasFeatureIris;
case TYPE_FACE:
return mHasFeatureFace;
default:
return false;
private String getErrorString(int modality, int error, int vendorCode) {
for (AuthenticatorWrapper authenticator : mAuthenticators) {
if (authenticator.modality == modality) {
// TODO(b/141025588): Refactor IBiometricServiceReceiver.aidl#onError(...) to not
// ask for a String error message, but derive it from the error code instead.
return "";
}
}
Slog.w(TAG, "Unable to get error string for modality: " + modality);
return null;
}
private void logDialogDismissed(int reason) {
@@ -1081,14 +1099,14 @@ public class BiometricService extends SystemService {
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
mStatusBarService.onBiometricAuthenticated();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleAuthenticationRejected(String failureReason) {
Slog.v(TAG, "handleAuthenticationRejected: " + failureReason);
private void handleAuthenticationRejected() {
Slog.v(TAG, "handleAuthenticationRejected()");
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
@@ -1097,7 +1115,8 @@ public class BiometricService extends SystemService {
return;
}
mStatusBarService.onBiometricAuthenticated(false, failureReason);
mStatusBarService.onBiometricError(TYPE_NONE,
BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
// TODO: This logic will need to be updated if BP is multi-modal
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
@@ -1112,8 +1131,9 @@ public class BiometricService extends SystemService {
}
}
private void handleAuthenticationTimedOut(String message) {
Slog.v(TAG, "handleAuthenticationTimedOut: " + message);
private void handleAuthenticationTimedOut(int modality, int error, int vendorCode) {
Slog.v(TAG, String.format("handleAuthenticationTimedOut(%d, %d, %d)", modality, error,
vendorCode));
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
@@ -1122,14 +1142,14 @@ public class BiometricService extends SystemService {
return;
}
mStatusBarService.onBiometricAuthenticated(false, message);
mStatusBarService.onBiometricError(modality, error, vendorCode);
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
private void handleOnError(int cookie, int error, String message) {
private void handleOnError(int cookie, int modality, int error, int vendorCode) {
Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1140,7 +1160,7 @@ public class BiometricService extends SystemService {
try {
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
mCurrentAuthSession.mErrorEscrow = error;
mCurrentAuthSession.mErrorStringEscrow = message;
mCurrentAuthSession.mVendorCodeEscrow = vendorCode;
if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
@@ -1148,20 +1168,20 @@ public class BiometricService extends SystemService {
if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mStatusBarService.onBiometricError(error, message);
mStatusBarService.onBiometricError(modality, error, vendorCode);
} else {
mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mStatusBarService.hideAuthenticationDialog();
} else {
mStatusBarService.onBiometricError(error, message);
mStatusBarService.onBiometricError(modality, error, vendorCode);
}
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
// the client and and clean up. The only error we should get here is
// ERROR_CANCELED due to another client kicking us out.
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
mStatusBarService.hideAuthenticationDialog();
mCurrentAuthSession = null;
} else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
@@ -1197,7 +1217,7 @@ public class BiometricService extends SystemService {
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mOpPackageName);
} else {
mPendingAuthSession.mClientReceiver.onError(error, message);
mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode);
mPendingAuthSession = null;
}
} else {
@@ -1261,8 +1281,10 @@ public class BiometricService extends SystemService {
case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
mCurrentAuthSession.mClientReceiver.onError(
mCurrentAuthSession.mModality,
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
getContext().getString(R.string.biometric_error_user_canceled));
0 /* vendorCode */
);
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */, false /* fromClient */);
@@ -1270,8 +1292,11 @@ public class BiometricService extends SystemService {
case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
case BiometricPrompt.DISMISSED_REASON_ERROR:
mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow,
mCurrentAuthSession.mErrorStringEscrow);
mCurrentAuthSession.mClientReceiver.onError(
mCurrentAuthSession.mModality,
mCurrentAuthSession.mErrorEscrow,
mCurrentAuthSession.mVendorCodeEscrow
);
break;
default:
@@ -1354,30 +1379,35 @@ public class BiometricService extends SystemService {
mPendingAuthSession = null;
mCurrentAuthSession.mState = STATE_AUTH_STARTED;
try {
int modality = TYPE_NONE;
it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
if (pair.getKey() == TYPE_FINGERPRINT) {
mFingerprintService.startPreparedClient(pair.getValue());
} else if (pair.getKey() == TYPE_IRIS) {
Slog.e(TAG, "Iris unsupported");
} else if (pair.getKey() == TYPE_FACE) {
mFaceService.startPreparedClient(pair.getValue());
} else {
Slog.e(TAG, "Unknown modality: " + pair.getKey());
int modality = TYPE_NONE;
it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
boolean foundAuthenticator = false;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
if (authenticator.modality == pair.getKey()) {
foundAuthenticator = true;
try {
authenticator.impl.startPreparedClient(pair.getValue());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
modality |= pair.getKey();
}
if (!foundAuthenticator) {
Slog.e(TAG, "Unknown modality: " + pair.getKey());
}
modality |= pair.getKey();
}
if (!continuing) {
if (!continuing) {
try {
mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId,
mCurrentAuthSession.mOpPackageName);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
}
@@ -1387,7 +1417,8 @@ public class BiometricService extends SystemService {
int callingUid, int callingPid, int callingUserId) {
mHandler.post(() -> {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
opPackageName);
final int modality = result.first;
final int error = result.second;
@@ -1400,23 +1431,7 @@ public class BiometricService extends SystemService {
} else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
// Check for errors, notify callback, and return
try {
final String hardwareUnavailable =
getContext().getString(R.string.biometric_error_hw_unavailable);
switch (error) {
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
receiver.onError(error,
getErrorString(modality, error, 0 /* vendorCode */));
break;
default:
Slog.e(TAG, "Unhandled error");
break;
}
receiver.onError(modality, error, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -1435,41 +1450,41 @@ public class BiometricService extends SystemService {
* modality/modalities to start authenticating with. authenticateInternal() should only be
* used for:
* 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
* invoked, shortly after which BiometricPrompt is shown and authentication starts
* invoked, shortly after which BiometricPrompt is shown and authentication starts
* 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
* and the user has pressed "try again"
* and the user has pressed "try again"
*/
private void authenticateInternal(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality) {
boolean requireConfirmation = bundle.getBoolean(
BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
if ((modality & TYPE_FACE) != 0) {
// Check if the user has forced confirmation to be required in Settings.
requireConfirmation = requireConfirmation
|| mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
}
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
Slog.d(TAG, "Creating auth session. Modality: " + modality
+ ", cookie: " + cookie
+ ", authenticators: " + authenticators);
final HashMap<Integer, Integer> modalities = new HashMap<>();
// If it's only device credential, we don't need to wait - LockSettingsService is
// always ready to check credential (SystemUI invokes that path).
if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
modalities.put(modality, cookie);
}
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
modality, requireConfirmation);
try {
boolean requireConfirmation = bundle.getBoolean(
BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
if ((modality & TYPE_FACE) != 0) {
// Check if the user has forced confirmation to be required in Settings.
requireConfirmation = requireConfirmation
|| mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
}
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
Slog.d(TAG, "Creating auth session. Modality: " + modality
+ ", cookie: " + cookie
+ ", authenticators: " + authenticators);
final HashMap<Integer, Integer> modalities = new HashMap<>();
// If it's only device credential, we don't need to wait - LockSettingsService is
// always ready to check credential (SystemUI invokes that path).
if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
modalities.put(modality, cookie);
}
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
modality, requireConfirmation);
if (authenticators == Authenticator.TYPE_CREDENTIAL) {
mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mCurrentAuthSession = mPendingAuthSession;
@@ -1484,19 +1499,10 @@ public class BiometricService extends SystemService {
mCurrentAuthSession.mOpPackageName);
} else {
mPendingAuthSession.mState = STATE_AUTH_CALLED;
// No polymorphism :(
if ((modality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.prepareForAuthentication(token, sessionId, userId,
mInternalReceiver, opPackageName, cookie,
callingUid, callingPid, callingUserId);
}
if ((modality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
mFaceService.prepareForAuthentication(requireConfirmation,
token, sessionId, userId, mInternalReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
for (AuthenticatorWrapper authenticator : mAuthenticators) {
authenticator.impl.prepareForAuthentication(requireConfirmation, token,
sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
callingPid, callingUserId);
}
}
} catch (RemoteException e) {
@@ -1517,11 +1523,10 @@ public class BiometricService extends SystemService {
try {
// Send error to client
mCurrentAuthSession.mClientReceiver.onError(
mCurrentAuthSession.mModality,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_user_canceled)
0 /* vendorCode */
);
mCurrentAuthSession = null;
mStatusBarService.hideAuthenticationDialog();
} catch (RemoteException e) {
@@ -1537,30 +1542,25 @@ public class BiometricService extends SystemService {
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
try {
if (mCurrentAuthSession == null) {
Slog.w(TAG, "Skipping cancelInternal");
return;
} else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
return;
}
if (mCurrentAuthSession == null) {
Slog.w(TAG, "Skipping cancelInternal");
return;
} else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
return;
}
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
// drivers have canceled authentication.
if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
// drivers have canceled authentication.
for (AuthenticatorWrapper authenticator : mAuthenticators) {
if ((authenticator.modality & mCurrentAuthSession.mModality) != 0) {
try {
authenticator.impl.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
}
if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
mFaceService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2019 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.face;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.IFaceService;
import android.os.IBinder;
import android.os.RemoteException;
/**
* TODO(b/141025588): Add JavaDoc.
*/
public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
private final IFaceService mFaceService;
public FaceAuthenticator(IFaceService faceService) {
mFaceService = faceService;
}
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
throws RemoteException {
mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId,
wrapperReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId);
}
@Override
public void startPreparedClient(int cookie) throws RemoteException {
mFaceService.startPreparedClient(cookie);
}
@Override
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
mFaceService.cancelAuthenticationFromService(token, opPackageName, callingUid, callingPid,
callingUserId, fromClient);
}
@Override
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
return mFaceService.isHardwareDetected(opPackageName);
}
@Override
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
return mFaceService.hasEnrolledFaces(userId, opPackageName);
}
@Override
public void resetLockout(byte[] token) throws RemoteException {
mFaceService.resetLockout(token);
}
@Override
public void setActiveUser(int uid) throws RemoteException {
mFaceService.setActiveUser(uid);
}
}

View File

@@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.RESET_FACE_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -538,7 +539,7 @@ public class FaceService extends BiometricServiceBase {
// TODO: refactor out common code here
@Override // Binder call
public boolean isHardwareDetected(long deviceId, String opPackageName) {
public boolean isHardwareDetected(String opPackageName) {
checkPermission(USE_BIOMETRIC_INTERNAL);
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
@@ -752,8 +753,7 @@ public class FaceService extends BiometricServiceBase {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onError(cookie, error,
FaceManager.getErrorString(getContext(), error, vendorCode));
getWrapperReceiver().onError(cookie, TYPE_FACE, error, vendorCode);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2019 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.fingerprint;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.os.IBinder;
import android.os.RemoteException;
/**
* TODO(b/141025588): Add JavaDoc.
*/
public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub {
private final IFingerprintService mFingerprintService;
public FingerprintAuthenticator(IFingerprintService fingerprintService) {
mFingerprintService = fingerprintService;
}
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
throws RemoteException {
mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
opPackageName, cookie, callingUid, callingPid, callingUserId);
}
@Override
public void startPreparedClient(int cookie) throws RemoteException {
mFingerprintService.startPreparedClient(cookie);
}
@Override
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
mFingerprintService.cancelAuthenticationFromService(token, opPackageName, callingUid,
callingPid, callingUserId, fromClient);
}
@Override
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
return mFingerprintService.isHardwareDetected(opPackageName);
}
@Override
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
return mFingerprintService.hasEnrolledFingerprints(userId, opPackageName);
}
@Override
public void resetLockout(byte[] token) throws RemoteException {
mFingerprintService.resetTimeout(token);
}
@Override
public void setActiveUser(int uid) throws RemoteException {
mFingerprintService.setActiveUser(uid);
}
}

View File

@@ -22,6 +22,7 @@ import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -350,7 +351,7 @@ public class FingerprintService extends BiometricServiceBase {
// TODO: refactor out common code here
@Override // Binder call
public boolean isHardwareDetected(long deviceId, String opPackageName) {
public boolean isHardwareDetected(String opPackageName) {
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
@@ -480,8 +481,7 @@ public class FingerprintService extends BiometricServiceBase {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onError(cookie, error,
FingerprintManager.getErrorString(getContext(), error, vendorCode));
getWrapperReceiver().onError(cookie, TYPE_FINGERPRINT, error, vendorCode);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2019 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.iris;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.iris.IIrisService;
import android.os.IBinder;
import android.os.RemoteException;
/**
* TODO(b/141025588): Add JavaDoc.
*/
public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
private final IIrisService mIrisService;
public IrisAuthenticator(IIrisService irisService) {
mIrisService = irisService;
}
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
throws RemoteException {
}
@Override
public void startPreparedClient(int cookie) throws RemoteException {
}
@Override
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
}
@Override
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
return false;
}
@Override
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
return false;
}
@Override
public void resetLockout(byte[] token) throws RemoteException {
}
@Override
public void setActiveUser(int uid) throws RemoteException {
}
}

View File

@@ -655,11 +655,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
public void onBiometricAuthenticated() {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.onBiometricAuthenticated(authenticated, failureReason);
mBar.onBiometricAuthenticated();
} catch (RemoteException ex) {
}
}
@@ -677,11 +677,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
public void onBiometricError(int errorCode, String error) {
public void onBiometricError(int modality, int error, int vendorCode) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.onBiometricError(errorCode, error);
mBar.onBiometricError(modality, error, vendorCode);
} catch (RemoteException ex) {
}
}

View File

@@ -33,7 +33,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,17 +42,15 @@ import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
@@ -68,8 +65,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@SmallTest
public class BiometricServiceTest {
@@ -98,71 +93,33 @@ public class BiometricServiceTest {
@Mock
private PackageManager mPackageManager;
@Mock
private AppOpsManager mAppOpsManager;
@Mock
IBiometricServiceReceiver mReceiver1;
@Mock
IBiometricServiceReceiver mReceiver2;
@Mock
FingerprintManager mFingerprintManager;
BiometricService.Injector mInjector;
@Mock
FaceManager mFaceManager;
private static class MockInjector extends BiometricService.Injector {
@Override
IActivityManager getActivityManagerService() {
return mock(IActivityManager.class);
}
@Override
IStatusBarService getStatusBarService() {
return mock(IStatusBarService.class);
}
@Override
IFingerprintService getFingerprintService() {
return mock(IFingerprintService.class);
}
@Override
IFaceService getFaceService() {
return mock(IFaceService.class);
}
@Override
BiometricService.SettingObserver getSettingObserver(Context context, Handler handler,
List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
return mock(BiometricService.SettingObserver.class);
}
@Override
KeyStore getKeyStore() {
return mock(KeyStore.class);
}
@Override
boolean isDebugEnabled(Context context, int userId) {
return false;
}
@Override
void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
// no-op for test
}
}
IBiometricAuthenticator mFingerprintAuthenticator;
@Mock
IBiometricAuthenticator mFaceAuthenticator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
.thenReturn(mFingerprintManager);
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
when(mInjector.getFingerprintAuthenticator()).thenReturn(mFingerprintAuthenticator);
when(mInjector.getFaceAuthenticator()).thenReturn(mFaceAuthenticator);
when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
mock(BiometricService.SettingObserver.class));
when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
when(mResources.getString(R.string.biometric_not_recognized))
@@ -172,61 +129,69 @@ public class BiometricServiceTest {
}
@Test
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception {
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE));
eq(BiometricAuthenticator.TYPE_NONE),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
eq(0 /* vendorCode */));
}
@Test
public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any());
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
eq(0 /* vendorCode */));
}
@Test
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception {
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
eq(BiometricAuthenticator.TYPE_NONE),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
}
@Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
// Disabled in user settings receives onError
@@ -235,7 +200,9 @@ public class BiometricServiceTest {
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
eq(BiometricAuthenticator.TYPE_NONE),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
// Enrolled, not disabled in settings, user requires confirmation in settings
resetReceiver();
@@ -245,8 +212,8 @@ public class BiometricServiceTest {
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), any(String.class));
verify(mBiometricService.mFaceService).prepareForAuthentication(
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(true) /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
@@ -265,7 +232,7 @@ public class BiometricServiceTest {
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mBiometricService.mFaceService).prepareForAuthentication(
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(false) /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
@@ -281,7 +248,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
// Start testing the happy path
@@ -295,8 +262,9 @@ public class BiometricServiceTest {
// Invokes <Modality>Service#prepareForAuthentication
ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mReceiver1, never()).onError(anyInt(), any(String.class));
verify(mBiometricService.mFingerprintService).prepareForAuthentication(
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
anyBoolean() /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
anyInt() /* userId */,
@@ -316,7 +284,7 @@ public class BiometricServiceTest {
BiometricService.STATE_AUTH_STARTED);
// startPreparedClient invoked
verify(mBiometricService.mFingerprintService)
verify(mBiometricService.mAuthenticators.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
// StatusBar showBiometricDialog invoked
@@ -337,8 +305,7 @@ public class BiometricServiceTest {
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI);
// Notify SystemUI hardware authenticated
verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(
eq(true) /* authenticated */, eq(null) /* failureReason */);
verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
// SystemUI sends callback with dismissed reason
mBiometricService.mInternalReceiver.onDialogDismissed(
@@ -355,7 +322,7 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, true /* allowDeviceCredential */);
waitForIdle();
@@ -409,8 +376,10 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
verify(mBiometricService.mStatusBarService)
.onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_NONE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_PAUSED);
@@ -426,15 +395,18 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
verify(mBiometricService.mStatusBarService)
.onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_NONE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_STARTED);
}
@Test
public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception {
public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, false /* allowDeviceCredential */);
@@ -451,15 +423,15 @@ public class BiometricServiceTest {
BiometricService.STATE_AUTH_STARTED);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED);
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
waitForIdle();
// Auth session doesn't become null until SystemUI responds that the animation is completed
assertNotNull(mBiometricService.mCurrentAuthSession);
// ERROR_CANCELED is not sent until SystemUI responded that animation is completed
verify(mReceiver1, never()).onError(
anyInt(), anyString());
verify(mReceiver2, never()).onError(anyInt(), any(String.class));
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI dialog closed
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -469,8 +441,9 @@ public class BiometricServiceTest {
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(ERROR_CANCELED));
eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -482,14 +455,17 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
ERROR_TIMEOUT);
0 /* vendorCode */);
waitForIdle();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_PAUSED);
verify(mBiometricService.mStatusBarService)
.onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT));
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
verify(mReceiver1, never()).onAuthenticationFailed();
@@ -524,22 +500,25 @@ public class BiometricServiceTest {
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireCOnfirmation */, false /* allowDeviceCredential */);
false /* requireConfirmation */, false /* allowDeviceCredential */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
ERROR_TIMEOUT);
0 /* vendorCode */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
ERROR_CANCELED);
0 /* vendorCode */);
waitForIdle();
// Client receives error immediately
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(ERROR_CANCELED));
eq(0 /* vendorCode */));
// Dialog is hidden immediately
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
// Auth session is over
@@ -558,26 +537,29 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
ERROR_UNABLE_TO_PROCESS);
0 /* vendorCode */);
waitForIdle();
// Sends error to SystemUI and does not notify client yet
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_ERROR_PENDING_SYSUI);
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(ERROR_UNABLE_TO_PROCESS));
eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
verify(mReceiver1, never()).onError(anyInt(), anyString());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI animation completed, client is notified, auth session is over
mBiometricService.mInternalReceiver
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(ERROR_UNABLE_TO_PROCESS));
eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -590,8 +572,9 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
ERROR_LOCKOUT);
0 /* vendorCode */);
waitForIdle();
// Pending auth session becomes current auth session, since device credential should
@@ -622,8 +605,9 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
ERROR_LOCKOUT);
0 /* vendorCode */);
waitForIdle();
// Error is sent to client
@@ -690,17 +674,18 @@ public class BiometricServiceTest {
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
verify(mReceiver1, never()).onError(anyInt(), anyString());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
ERROR_CANCELED);
0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
verify(mReceiver1, never()).onError(anyInt(), anyString());
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@Test
@@ -714,15 +699,17 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
ERROR_LOCKOUT);
0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
eq(ERROR_LOCKOUT));
eq(0 /* vendorCode */));
}
@Test
@@ -736,15 +723,17 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
ERROR_UNABLE_TO_PROCESS);
0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_ERROR_PENDING_SYSUI,
mBiometricService.mCurrentAuthSession.mState);
verify(mBiometricService.mStatusBarService).onBiometricError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
eq(ERROR_UNABLE_TO_PROCESS));
eq(0 /* vendorCode */));
}
@Test
@@ -758,9 +747,10 @@ public class BiometricServiceTest {
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(ERROR_USER_CANCELED));
verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService(
eq(0 /* vendorCode */));
verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -778,13 +768,15 @@ public class BiometricServiceTest {
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
ERROR_TIMEOUT);
0 /* vendorCode */);
mBiometricService.mInternalReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_NEGATIVE);
waitForIdle();
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
verify(mBiometricService.mAuthenticators.get(0).impl,
never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -794,20 +786,23 @@ public class BiometricServiceTest {
}
@Test
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception {
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, false /* allowDeviceCredential */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
ERROR_TIMEOUT);
0 /* vendorCode */);
mBiometricService.mInternalReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
waitForIdle();
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
verify(mBiometricService.mAuthenticators.get(0).impl,
never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -830,7 +825,8 @@ public class BiometricServiceTest {
waitForIdle();
// doesn't send cancel to HAL
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
verify(mBiometricService.mAuthenticators.get(0).impl,
never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -838,8 +834,9 @@ public class BiometricServiceTest {
anyInt(),
anyBoolean());
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(ERROR_USER_CANCELED));
eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -863,7 +860,7 @@ public class BiometricServiceTest {
// Helper methods
private void setupAuthForOnly(int modality) {
private void setupAuthForOnly(int modality) throws RemoteException {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
@@ -871,17 +868,17 @@ public class BiometricServiceTest {
if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
} else if (modality == BiometricAuthenticator.TYPE_FACE) {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
} else {
fail("Unknown modality: " + modality);
}
mBiometricService = new BiometricService(mContext, new MockInjector());
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);