diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 0ec812fe03509..698876b9c59ea 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -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 diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 27c04b4073156..c8bf570e1bc80 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -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 */ diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index cbe8a052db2fc..9d427c8d8bab8 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -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; diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index cf86e25112d2d..9c51b52467495 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -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); }); } diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl new file mode 100644 index 0000000000000..987d197996447 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl @@ -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); +} diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 6a3bf38a97e16..06336a55ac5e0 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -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); diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index 22ef33e86e175..c960049438f1c 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -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. diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index 66b6e896fc136..61310f302fe44 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -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. diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 12b285a0f0ab3..55ebe285af1e5 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -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(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index b6a0afbf716cf..68a4aef7af081 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -67,7 +67,7 @@ interface IFaceService { List 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); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index d0622c88b4caf..22f8590a4e979 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -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(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index dd6b29d87d678..1a7e128567533 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -71,7 +71,7 @@ interface IFingerprintService { List 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); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index d9e2ba3e0975d..317469e58245a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -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(); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 659134adec783..499a4d2fb9497 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -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(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index cdc2623d34fd3..b758731000259 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -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); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c9dc08e78afb5..34f543702d96c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -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++) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index e1eb3b0c81b22..b089b740fc476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -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 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 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 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(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index eb198c6ad4f72..8c9ae71dc0f31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -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 diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 4f1db3c96faff..619c21e7646bc 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -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 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 result = checkAndGetBiometricModality(userId); + final Pair 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 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 checkAndGetBiometricModality(int userId) { + private Pair 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 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 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 result = checkAndGetBiometricModality(userId); + final Pair 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 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 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 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 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"); } } } diff --git a/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java new file mode 100644 index 0000000000000..5df5b29d58a86 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 1b132120f7098..a0c8e2325f770 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -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); } } } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java new file mode 100644 index 0000000000000..6150de151ccc9 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index d85af2eb0b325..44797ad97b37f 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -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); } } } diff --git a/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java new file mode 100644 index 0000000000000..c44b8e7227e3d --- /dev/null +++ b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java @@ -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 { + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 3c1a6afccda1b..489c34359645e 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -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) { } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 4aeeb0af1bf82..ec47a959de305 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -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 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 Service#prepareForAuthentication ArgumentCaptor 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);