Loading services/core/java/com/android/server/biometrics/BiometricService.java +104 −94 Original line number Diff line number Diff line Loading @@ -707,7 +707,6 @@ public class BiometricService extends SystemService { checkPermission(); } if (!Utils.isValidAuthenticatorConfig(authenticators)) { throw new SecurityException("Invalid authenticator configuration"); } Loading @@ -720,19 +719,6 @@ public class BiometricService extends SystemService { try { biometricConstantsResult = checkAndGetAuthenticators(userId, bundle, opPackageName, false /* checkDevicePolicyManager */).second; if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS && Utils.isDeviceCredentialAllowed(bundle)) { // If there's an issue with biometrics, but device credential is allowed and // set up, return SUCCESS. If device credential isn't set up either, return // ERROR_NO_DEVICE_CREDENTIAL. if (mTrustManager.isDeviceSecure(userId)) { biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS; } else { biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL; } } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } finally { Loading Loading @@ -1022,9 +1008,8 @@ public class BiometricService extends SystemService { } /** * Checks if there are any available biometrics, and returns the modality. This method also * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. * Depending on the requested authentication (credential/biometric combination), checks their * availability. * * @param userId the user to check for * @param bundle passed from {@link BiometricPrompt} Loading @@ -1037,24 +1022,27 @@ public class BiometricService extends SystemService { * {@link BiometricAuthenticator#TYPE_FACE} * and the error containing one of the {@link BiometricConstants} errors. * * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers * TODO(kchyn) should return Pair<Integer, Integer> with `first` being an actual bitfield * taking BiometricAuthenticator#TYPE_CREDENTIAL as well. * */ private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle, String opPackageName, boolean checkDevicePolicyManager) throws RemoteException { if (!Utils.isBiometricAllowed(bundle) && Utils.isDeviceCredentialAllowed(bundle) && !mTrustManager.isDeviceSecure(userId)) { // If only device credential is being checked, and the user doesn't have one set up return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL); } // Assuming that authenticators are listed in priority-order, the rest of this function // will attempt to find the first authenticator that's as strong or stronger than the // requested strength, available, enrolled, and enabled. The tricky part is returning the // correct error. Error strings that are modality-specific should also respect the final boolean biometricRequested = Utils.isBiometricAllowed(bundle); final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle); final boolean biometricOk; final boolean credentialOk = mTrustManager.isDeviceSecure(userId); // Assuming that biometric authenticators are listed in priority-order, the rest of this // function will attempt to find the first authenticator that's as strong or stronger than // the requested strength, available, enrolled, and enabled. The tricky part is returning // the correct error. Error strings that are modality-specific should also respect the // priority-order. // Find first authenticator that's strong enough, detected, enrolled, and enabled. // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled. boolean disabledByDevicePolicy = false; boolean hasSufficientStrength = false; boolean isHardwareDetected = false; boolean hasTemplatesEnrolled = false; Loading @@ -1065,52 +1053,96 @@ public class BiometricService extends SystemService { for (AuthenticatorWrapper authenticator : mAuthenticators) { final int actualStrength = authenticator.getActualStrength(); final int requestedStrength = Utils.getPublicBiometricStrength(bundle); if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) { if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { disabledByDevicePolicy = true; continue; } disabledByDevicePolicy = false; if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) { continue; } hasSufficientStrength = true; modality = authenticator.modality; if (authenticator.impl.isHardwareDetected(opPackageName)) { if (!authenticator.impl.isHardwareDetected(opPackageName)) { continue; } isHardwareDetected = true; if (firstHwAvailable == TYPE_NONE) { // Store the first one since we want to return the error in correct // priority order. firstHwAvailable = modality; firstHwAvailable = authenticator.modality; } if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { continue; } if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { hasTemplatesEnrolled = true; // If the device policy manager disables a specific biometric, skip it. if (checkDevicePolicyManager && isBiometricDisabledByDevicePolicy(modality, userId)) { if (!isEnabledForApp(authenticator.modality, userId)) { continue; } if (isEnabledForApp(modality, userId)) { enabledForApps = true; modality = authenticator.modality; break; } } } } } biometricOk = !disabledByDevicePolicy && hasSufficientStrength && isHardwareDetected && hasTemplatesEnrolled && enabledForApps; Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId + " checkDevicePolicyManager=" + checkDevicePolicyManager + " isHardwareDetected=" + isHardwareDetected + " hasTemplatesEnrolled=" + hasTemplatesEnrolled + " enabledForApps=" + enabledForApps); // Check error conditions if (!hasSufficientStrength) { + " enabledForApps=" + enabledForApps + " disabledByDevicePolicy=" + disabledByDevicePolicy); if (biometricRequested && credentialRequested) { if (credentialOk || biometricOk) { if (!biometricOk) { // If there's a problem with biometrics but device credential is // allowed, only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } else { return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } } else if (biometricRequested) { if (biometricOk) { return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } else if (disabledByDevicePolicy) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasSufficientStrength) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } else if (!isHardwareDetected) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasTemplatesEnrolled) { // Return the modality here so the correct error string can be sent. This error is // preferred over !enabledForApps return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } else if (!enabledForApps) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else { Slog.e(TAG, "Unexpected case"); return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } } else if (credentialRequested) { if (credentialOk) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_SUCCESS); } else { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL); } } else { Slog.e(TAG, "No authenticators requested"); return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } private boolean isEnabledForApp(int modality, int userId) { Loading Loading @@ -1549,42 +1581,20 @@ public class BiometricService extends SystemService { int callingUid, int callingPid, int callingUserId) { mHandler.post(() -> { int modality = TYPE_NONE; int result; try { final boolean checkDevicePolicyManager = bundle.getBoolean( BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false); final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle, opPackageName, checkDevicePolicyManager); modality = pair.first; result = pair.second; } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; } final int modality = pair.first; final int result = pair.second; try { if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) { // If the app allowed device credential but the user hasn't set it up yet, // return this error. receiver.onError(modality, result, 0 /* vendorCode */); } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) { if (Utils.isDeviceCredentialAllowed(bundle)) { // If there's a problem with biometrics but device credential is allowed, // only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); if (result == BiometricConstants.BIOMETRIC_SUCCESS) { authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); } else { receiver.onError(modality, result, 0 /* vendorCode */); } } else { // BIOMETRIC_SUCCESS, proceed to authentication authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } Loading services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +40 −22 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class BiometricServiceTest { public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); Loading Loading @@ -466,6 +467,22 @@ public class BiometricServiceTest { BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); } @Test public void testAuthenticate_no_Biometrics_noCredential() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); waitForIdle(); verify(mReceiver1).onError(anyInt() /* modality */, eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS)/* error */, eq(0) /* vendorCode */); } @Test public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused() throws Exception { Loading Loading @@ -1216,7 +1233,8 @@ public class BiometricServiceTest { when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertEquals(mBiometricService.mPendingAuthSession.mState, BiometricService.STATE_AUTH_CALLED); Loading @@ -1233,7 +1251,8 @@ public class BiometricServiceTest { when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */)) .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertEquals(mBiometricService.mPendingAuthSession.mState, BiometricService.STATE_AUTH_CALLED); Loading @@ -1247,27 +1266,24 @@ public class BiometricServiceTest { public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertNotNull(mBiometricService.mCurrentAuthSession); assertEquals(mBiometricService.mCurrentAuthSession.mState, BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL); } verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_NONE), eq(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0) /* vendorCode */); @Test public void testWorkAuthentication_faceFailsIfDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2, Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); waitForIdle(); assertNotNull(mBiometricService.mCurrentAuthSession); assertEquals(mBiometricService.mCurrentAuthSession.mState, BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL); verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt()); } // Helper methods Loading Loading @@ -1376,33 +1392,35 @@ public class BiometricServiceTest { 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, createTestBiometricPromptBundle(requireConfirmation, authenticators)); createTestBiometricPromptBundle(requireConfirmation, authenticators, false /* checkDevicePolicy */)); } private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service, IBiometricServiceReceiver receiver) throws Exception { final Bundle bundle = new Bundle(); bundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, true); bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true); bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); IBiometricServiceReceiver receiver, Integer authenticators) throws Exception { service.authenticate( new Binder() /* token */, 0 /* sessionId */, 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, bundle); createTestBiometricPromptBundle(false /* requireConfirmation */, authenticators, true /* checkDevicePolicy */)); } private static Bundle createTestBiometricPromptBundle( boolean requireConfirmation, Integer authenticators) { Integer authenticators, boolean checkDevicePolicy) { final Bundle bundle = new Bundle(); bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation); if (authenticators != null) { bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); } if (checkDevicePolicy) { bundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, true); } return bundle; } Loading Loading
services/core/java/com/android/server/biometrics/BiometricService.java +104 −94 Original line number Diff line number Diff line Loading @@ -707,7 +707,6 @@ public class BiometricService extends SystemService { checkPermission(); } if (!Utils.isValidAuthenticatorConfig(authenticators)) { throw new SecurityException("Invalid authenticator configuration"); } Loading @@ -720,19 +719,6 @@ public class BiometricService extends SystemService { try { biometricConstantsResult = checkAndGetAuthenticators(userId, bundle, opPackageName, false /* checkDevicePolicyManager */).second; if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS && Utils.isDeviceCredentialAllowed(bundle)) { // If there's an issue with biometrics, but device credential is allowed and // set up, return SUCCESS. If device credential isn't set up either, return // ERROR_NO_DEVICE_CREDENTIAL. if (mTrustManager.isDeviceSecure(userId)) { biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS; } else { biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL; } } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } finally { Loading Loading @@ -1022,9 +1008,8 @@ public class BiometricService extends SystemService { } /** * Checks if there are any available biometrics, and returns the modality. This method also * returns errors through the callback (no biometric feature, hardware not detected, no * templates enrolled, etc). This service must not start authentication if errors are sent. * Depending on the requested authentication (credential/biometric combination), checks their * availability. * * @param userId the user to check for * @param bundle passed from {@link BiometricPrompt} Loading @@ -1037,24 +1022,27 @@ public class BiometricService extends SystemService { * {@link BiometricAuthenticator#TYPE_FACE} * and the error containing one of the {@link BiometricConstants} errors. * * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers * TODO(kchyn) should return Pair<Integer, Integer> with `first` being an actual bitfield * taking BiometricAuthenticator#TYPE_CREDENTIAL as well. * */ private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle, String opPackageName, boolean checkDevicePolicyManager) throws RemoteException { if (!Utils.isBiometricAllowed(bundle) && Utils.isDeviceCredentialAllowed(bundle) && !mTrustManager.isDeviceSecure(userId)) { // If only device credential is being checked, and the user doesn't have one set up return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL); } // Assuming that authenticators are listed in priority-order, the rest of this function // will attempt to find the first authenticator that's as strong or stronger than the // requested strength, available, enrolled, and enabled. The tricky part is returning the // correct error. Error strings that are modality-specific should also respect the final boolean biometricRequested = Utils.isBiometricAllowed(bundle); final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle); final boolean biometricOk; final boolean credentialOk = mTrustManager.isDeviceSecure(userId); // Assuming that biometric authenticators are listed in priority-order, the rest of this // function will attempt to find the first authenticator that's as strong or stronger than // the requested strength, available, enrolled, and enabled. The tricky part is returning // the correct error. Error strings that are modality-specific should also respect the // priority-order. // Find first authenticator that's strong enough, detected, enrolled, and enabled. // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled. boolean disabledByDevicePolicy = false; boolean hasSufficientStrength = false; boolean isHardwareDetected = false; boolean hasTemplatesEnrolled = false; Loading @@ -1065,52 +1053,96 @@ public class BiometricService extends SystemService { for (AuthenticatorWrapper authenticator : mAuthenticators) { final int actualStrength = authenticator.getActualStrength(); final int requestedStrength = Utils.getPublicBiometricStrength(bundle); if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) { if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { disabledByDevicePolicy = true; continue; } disabledByDevicePolicy = false; if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) { continue; } hasSufficientStrength = true; modality = authenticator.modality; if (authenticator.impl.isHardwareDetected(opPackageName)) { if (!authenticator.impl.isHardwareDetected(opPackageName)) { continue; } isHardwareDetected = true; if (firstHwAvailable == TYPE_NONE) { // Store the first one since we want to return the error in correct // priority order. firstHwAvailable = modality; firstHwAvailable = authenticator.modality; } if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { continue; } if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { hasTemplatesEnrolled = true; // If the device policy manager disables a specific biometric, skip it. if (checkDevicePolicyManager && isBiometricDisabledByDevicePolicy(modality, userId)) { if (!isEnabledForApp(authenticator.modality, userId)) { continue; } if (isEnabledForApp(modality, userId)) { enabledForApps = true; modality = authenticator.modality; break; } } } } } biometricOk = !disabledByDevicePolicy && hasSufficientStrength && isHardwareDetected && hasTemplatesEnrolled && enabledForApps; Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId + " checkDevicePolicyManager=" + checkDevicePolicyManager + " isHardwareDetected=" + isHardwareDetected + " hasTemplatesEnrolled=" + hasTemplatesEnrolled + " enabledForApps=" + enabledForApps); // Check error conditions if (!hasSufficientStrength) { + " enabledForApps=" + enabledForApps + " disabledByDevicePolicy=" + disabledByDevicePolicy); if (biometricRequested && credentialRequested) { if (credentialOk || biometricOk) { if (!biometricOk) { // If there's a problem with biometrics but device credential is // allowed, only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } else { return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } } else if (biometricRequested) { if (biometricOk) { return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } else if (disabledByDevicePolicy) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasSufficientStrength) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); } else if (!isHardwareDetected) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else if (!hasTemplatesEnrolled) { // Return the modality here so the correct error string can be sent. This error is // preferred over !enabledForApps return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } else if (!enabledForApps) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } else { Slog.e(TAG, "Unexpected case"); return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } } else if (credentialRequested) { if (credentialOk) { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_SUCCESS); } else { return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL); } } else { Slog.e(TAG, "No authenticators requested"); return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); } return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); } private boolean isEnabledForApp(int modality, int userId) { Loading Loading @@ -1549,42 +1581,20 @@ public class BiometricService extends SystemService { int callingUid, int callingPid, int callingUserId) { mHandler.post(() -> { int modality = TYPE_NONE; int result; try { final boolean checkDevicePolicyManager = bundle.getBoolean( BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false); final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle, opPackageName, checkDevicePolicyManager); modality = pair.first; result = pair.second; } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; } final int modality = pair.first; final int result = pair.second; try { if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) { // If the app allowed device credential but the user hasn't set it up yet, // return this error. receiver.onError(modality, result, 0 /* vendorCode */); } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) { if (Utils.isDeviceCredentialAllowed(bundle)) { // If there's a problem with biometrics but device credential is allowed, // only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); if (result == BiometricConstants.BIOMETRIC_SUCCESS) { authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); } else { receiver.onError(modality, result, 0 /* vendorCode */); } } else { // BIOMETRIC_SUCCESS, proceed to authentication authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } Loading
services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +40 −22 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class BiometricServiceTest { public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); Loading Loading @@ -466,6 +467,22 @@ public class BiometricServiceTest { BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); } @Test public void testAuthenticate_no_Biometrics_noCredential() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); waitForIdle(); verify(mReceiver1).onError(anyInt() /* modality */, eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS)/* error */, eq(0) /* vendorCode */); } @Test public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused() throws Exception { Loading Loading @@ -1216,7 +1233,8 @@ public class BiometricServiceTest { when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertEquals(mBiometricService.mPendingAuthSession.mState, BiometricService.STATE_AUTH_CALLED); Loading @@ -1233,7 +1251,8 @@ public class BiometricServiceTest { when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */)) .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertEquals(mBiometricService.mPendingAuthSession.mState, BiometricService.STATE_AUTH_CALLED); Loading @@ -1247,27 +1266,24 @@ public class BiometricServiceTest { public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); assertNotNull(mBiometricService.mCurrentAuthSession); assertEquals(mBiometricService.mCurrentAuthSession.mState, BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL); } verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_NONE), eq(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0) /* vendorCode */); @Test public void testWorkAuthentication_faceFailsIfDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2, Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); waitForIdle(); assertNotNull(mBiometricService.mCurrentAuthSession); assertEquals(mBiometricService.mCurrentAuthSession.mState, BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL); verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt()); } // Helper methods Loading Loading @@ -1376,33 +1392,35 @@ public class BiometricServiceTest { 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, createTestBiometricPromptBundle(requireConfirmation, authenticators)); createTestBiometricPromptBundle(requireConfirmation, authenticators, false /* checkDevicePolicy */)); } private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service, IBiometricServiceReceiver receiver) throws Exception { final Bundle bundle = new Bundle(); bundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, true); bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true); bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); IBiometricServiceReceiver receiver, Integer authenticators) throws Exception { service.authenticate( new Binder() /* token */, 0 /* sessionId */, 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, bundle); createTestBiometricPromptBundle(false /* requireConfirmation */, authenticators, true /* checkDevicePolicy */)); } private static Bundle createTestBiometricPromptBundle( boolean requireConfirmation, Integer authenticators) { Integer authenticators, boolean checkDevicePolicy) { final Bundle bundle = new Bundle(); bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation); if (authenticators != null) { bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); } if (checkDevicePolicy) { bundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, true); } return bundle; } Loading