From f2da1a1178cc1a15e2b2ee532a495503c4112eb8 Mon Sep 17 00:00:00 2001 From: Ilya Matyukhin Date: Wed, 25 Sep 2019 15:31:03 -0700 Subject: [PATCH 1/2] Prepare BiometricService for migration to a module Created an interface, IBiometricAuthenticator, that encapsulates fingerprint, iris, and face services. Removed direct references to these services from BiometricService. The interface will allow new biometric authenticators to be integrated with BiometricService without modifying its code. Bug: 141025588 Test: Face Unlock works E2E Test: Works with BiometricPromptDemo Test: atest BiometricServiceTest Change-Id: I564ccdccbfe25b2c41f92f7e91eedde6acdeeed4 --- .../hardware/biometrics/BiometricManager.java | 2 +- .../biometrics/IBiometricAuthenticator.aidl | 58 +++ .../biometrics/IBiometricService.aidl | 8 +- .../android/hardware/face/FaceManager.java | 3 +- .../android/hardware/face/IFaceService.aidl | 2 +- .../fingerprint/FingerprintManager.java | 3 +- .../fingerprint/IFingerprintService.aidl | 2 +- .../server/biometrics/BiometricService.java | 409 +++++++++--------- .../biometrics/face/FaceAuthenticator.java | 75 ++++ .../server/biometrics/face/FaceService.java | 2 +- .../fingerprint/FingerprintAuthenticator.java | 75 ++++ .../fingerprint/FingerprintService.java | 2 +- .../biometrics/iris/IrisAuthenticator.java | 68 +++ .../biometrics/BiometricServiceTest.java | 141 +++--- 14 files changed, 547 insertions(+), 303 deletions(-) create mode 100644 core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl create mode 100644 services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java create mode 100644 services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java create mode 100644 services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java 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/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/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/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 4f1db3c96faff..2b45f1970ef48 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 @@ -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 @@ -347,21 +339,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; } } @@ -666,7 +660,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 +670,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 +713,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 +728,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 +752,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 +843,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 +864,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 +903,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 +921,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 +964,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 +977,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) { @@ -1354,30 +1361,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 +1399,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; @@ -1435,41 +1448,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 +1497,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) { @@ -1537,30 +1541,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..de6ee8b20c0e2 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -538,7 +538,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(), 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..d59fcbf7c4d8e 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -350,7 +350,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())) { 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/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 4aeeb0af1bf82..dd86123ec4230 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,13 +129,14 @@ 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 */, @@ -191,9 +149,9 @@ public class BiometricServiceTest { @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 */, @@ -204,12 +162,13 @@ public class BiometricServiceTest { } @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 */, @@ -223,10 +182,10 @@ public class BiometricServiceTest { 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 @@ -246,7 +205,7 @@ public class BiometricServiceTest { false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), any(String.class)); - verify(mBiometricService.mFaceService).prepareForAuthentication( + verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication( eq(true) /* requireConfirmation */, any(IBinder.class), anyLong() /* sessionId */, @@ -265,7 +224,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 +240,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 @@ -296,7 +255,8 @@ 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(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication( + anyBoolean() /* requireConfirmation */, any(IBinder.class), anyLong() /* sessionId */, anyInt() /* userId */, @@ -316,7 +276,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 @@ -355,7 +315,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(); @@ -434,7 +394,8 @@ public class BiometricServiceTest { } @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 */); @@ -524,7 +485,7 @@ 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), @@ -760,7 +721,7 @@ public class BiometricServiceTest { verify(mReceiver1).onError( eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED), eq(ERROR_USER_CANCELED)); - verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService( + verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService( any(), any(), anyInt(), @@ -784,7 +745,8 @@ public class BiometricServiceTest { BiometricPrompt.DISMISSED_REASON_NEGATIVE); waitForIdle(); - verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService( + verify(mBiometricService.mAuthenticators.get(0).impl, + never()).cancelAuthenticationFromService( any(), any(), anyInt(), @@ -794,7 +756,8 @@ 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 */); @@ -807,7 +770,8 @@ public class BiometricServiceTest { BiometricPrompt.DISMISSED_REASON_USER_CANCEL); waitForIdle(); - verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService( + verify(mBiometricService.mAuthenticators.get(0).impl, + never()).cancelAuthenticationFromService( any(), any(), anyInt(), @@ -830,7 +794,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(), @@ -863,7 +828,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 +836,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); From 0f9da353b5cc6acc8e42b49d51c96eb527680c3b Mon Sep 17 00:00:00 2001 From: Ilya Matyukhin Date: Thu, 3 Oct 2019 14:10:01 -0700 Subject: [PATCH 2/2] Remove strings from low level onError(...) calls In order to get a correctly translated error messages, getString(...) should be called on the application context, as opposed to the system context. This is because the system context is unaware of the user's locale. Bug: 141025588 Test: Face Unlock works E2E Test: Works with BiometricPromptDemo Test: atest BiometricServiceTest Test: atest AuthControllerTest Test: atest CommandQueueTest Change-Id: Ic228bb7ebb0d6a4ebaf96b9f1d2d70ed4e9dd79a --- .../biometrics/BiometricAuthenticator.java | 13 +- .../biometrics/BiometricConstants.java | 7 ++ .../hardware/biometrics/BiometricPrompt.java | 20 +++- .../biometrics/IBiometricServiceReceiver.aidl | 2 +- .../IBiometricServiceReceiverInternal.aidl | 2 +- .../internal/statusbar/IStatusBar.aidl | 8 +- .../internal/statusbar/IStatusBarService.aidl | 8 +- .../systemui/biometrics/AuthController.java | 58 ++++++--- .../systemui/statusbar/CommandQueue.java | 33 +++--- .../biometrics/AuthControllerTest.java | 59 +++++---- .../systemui/statusbar/CommandQueueTest.java | 14 +-- .../server/biometrics/BiometricService.java | 97 +++++++-------- .../server/biometrics/face/FaceService.java | 4 +- .../fingerprint/FingerprintService.java | 4 +- .../statusbar/StatusBarManagerService.java | 8 +- .../biometrics/BiometricServiceTest.java | 112 +++++++++++------- 16 files changed, 280 insertions(+), 169 deletions(-) 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/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/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/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index c8ba52a63151d..23c6acc606fd3 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -154,12 +154,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 d6a8f906197da..2b1c807c245af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -273,9 +273,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() { } /** @@ -757,12 +757,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(); } } @@ -774,9 +771,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(); } } @@ -1045,13 +1046,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: @@ -1060,9 +1057,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 1bd01e166ddb8..ccb754cc240df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -379,10 +379,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 @@ -395,11 +394,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 2b45f1970ef48..619c21e7646bc 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -168,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; @@ -254,7 +254,7 @@ public class BiometricService extends SystemService { } case MSG_ON_AUTHENTICATION_REJECTED: { - handleAuthenticationRejected((String) msg.obj /* failureReason */); + handleAuthenticationRejected(); break; } @@ -262,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; } @@ -323,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; } @@ -515,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(); } } @@ -1088,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). @@ -1104,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) { @@ -1119,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). @@ -1129,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 @@ -1147,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 @@ -1155,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) { @@ -1204,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 { @@ -1268,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 */); @@ -1277,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: @@ -1413,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); } @@ -1521,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) { 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 de6ee8b20c0e2..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; @@ -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/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index d59fcbf7c4d8e..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; @@ -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/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 65bb2342d5040..6f927a5465f09 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -623,11 +623,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) { } } @@ -645,11 +645,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 dd86123ec4230..ec47a959de305 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -143,7 +143,9 @@ public class BiometricServiceTest { 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 @@ -158,7 +160,9 @@ public class BiometricServiceTest { 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 @@ -175,7 +179,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 */)); } @Test @@ -194,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(); @@ -204,7 +212,7 @@ public class BiometricServiceTest { invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, false /* allowDeviceCredential */); waitForIdle(); - verify(mReceiver1, never()).onError(anyInt(), any(String.class)); + verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication( eq(true) /* requireConfirmation */, any(IBinder.class), @@ -254,7 +262,7 @@ public class BiometricServiceTest { // Invokes Service#prepareForAuthentication ArgumentCaptor cookieCaptor = ArgumentCaptor.forClass(Integer.class); - verify(mReceiver1, never()).onError(anyInt(), any(String.class)); + verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication( anyBoolean() /* requireConfirmation */, any(IBinder.class), @@ -297,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( @@ -369,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); @@ -386,8 +395,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_STARTED); @@ -412,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(); @@ -430,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); } @@ -443,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(); @@ -489,18 +504,21 @@ public class BiometricServiceTest { 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 @@ -519,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); } @@ -551,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 @@ -583,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 @@ -651,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 @@ -675,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 @@ -697,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 @@ -719,8 +747,9 @@ 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)); + eq(0 /* vendorCode */)); verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService( any(), any(), @@ -739,8 +768,9 @@ 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(); @@ -764,8 +794,9 @@ 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_USER_CANCEL); waitForIdle(); @@ -803,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); }