diff options
7 files changed, 136 insertions, 27 deletions
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 61d87026b6e9..8975191b54c1 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -162,6 +162,13 @@ public interface BiometricConstants { * @hide */ int BIOMETRIC_ERROR_POWER_PRESSED = 19; + + /** + * Mandatory biometrics is not in effect. + * @hide + */ + int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = 20; + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index de1cac47ff46..9bc46b9f382a 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -80,6 +80,20 @@ public class BiometricManager { BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; /** + * Lockout error. + * @hide + */ + public static final int BIOMETRIC_ERROR_LOCKOUT = + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT; + + /** + * Mandatory biometrics is not effective. + * @hide + */ + public static final int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = + BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + + /** * A security vulnerability has been discovered and the sensor is unavailable until a * security update has addressed this issue. This error can be received if for example, * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the @@ -113,7 +127,9 @@ public class BiometricManager { BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, BIOMETRIC_ERROR_NO_HARDWARE, - BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED}) + BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, + BIOMETRIC_ERROR_LOCKOUT, + BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE}) @Retention(RetentionPolicy.SOURCE) public @interface BiometricError {} diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index 0bd22f3da67f..f0da67bd9f9a 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -77,13 +77,15 @@ class PreAuthInfo { private final int mBiometricStrengthRequested; private final BiometricCameraManager mBiometricCameraManager; private final boolean mOnlyMandatoryBiometricsRequested; + private final boolean mIsMandatoryBiometricsAuthentication; private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, PromptInfo promptInfo, int userId, Context context, BiometricCameraManager biometricCameraManager, - boolean isOnlyMandatoryBiometricsRequested) { + boolean isOnlyMandatoryBiometricsRequested, + boolean isMandatoryBiometricsAuthentication) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; mBiometricCameraManager = biometricCameraManager; @@ -97,6 +99,7 @@ class PreAuthInfo { this.userId = userId; this.context = context; this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested; + this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication; } static PreAuthInfo create(ITrustManager trustManager, @@ -110,10 +113,12 @@ class PreAuthInfo { final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators() == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; + boolean isMandatoryBiometricsAuthentication = false; if (dropCredentialFallback(promptInfo.getAuthenticators(), settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( userId), trustManager)) { + isMandatoryBiometricsAuthentication = true; promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); } @@ -166,7 +171,8 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId, - context, biometricCameraManager, isOnlyMandatoryBiometricsRequested); + context, biometricCameraManager, isOnlyMandatoryBiometricsRequested, + isMandatoryBiometricsAuthentication); } private static boolean dropCredentialFallback(int authenticators, @@ -387,25 +393,6 @@ class PreAuthInfo { status = CREDENTIAL_NOT_ENROLLED; } } - } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) { - if (!eligibleSensors.isEmpty()) { - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } - - if (modality == TYPE_FACE && cameraPrivacyEnabled) { - // If the only modality requested is face, credential is unavailable, - // and the face sensor privacy is enabled then return - // BIOMETRIC_SENSOR_PRIVACY_ENABLED. - // - // Note: This sensor will not be eligible for calls to authenticate. - status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; - } else { - status = AUTHENTICATOR_OK; - } - } else { - status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; - } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { for (BiometricSensor sensor : eligibleSensors) { @@ -434,6 +421,9 @@ class PreAuthInfo { } else if (credentialRequested) { modality |= TYPE_CREDENTIAL; status = credentialAvailable ? AUTHENTICATOR_OK : CREDENTIAL_NOT_ENROLLED; + } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested + && !mIsMandatoryBiometricsAuthentication) { + status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; } else { // This should not be possible via the public API surface and is here mainly for // "correctness". An exception should have been thrown before getting here. diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index df29ca45930e..3e4e7dd771d2 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -35,6 +35,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; +import static com.android.server.biometrics.PreAuthInfo.MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +49,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; @@ -309,11 +311,16 @@ public class Utils { break; case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT: case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: - biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; + biometricManagerCode = Flags.mandatoryBiometrics() + ? BiometricManager.BIOMETRIC_ERROR_LOCKOUT + : BiometricManager.BIOMETRIC_SUCCESS; break; case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; break; + case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -375,6 +382,8 @@ public class Utils { return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; case BIOMETRIC_SENSOR_PRIVACY_ENABLED: return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; + case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR: + return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: 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 a4222ff5650b..d2961bc8a90f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -79,6 +79,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -1488,15 +1489,30 @@ public class BiometricServiceTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutTimed() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_TIMED); } @Test + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenLockoutPermanent() throws Exception { testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_PERMANENT); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutTimed_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_TIMED); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testCanAuthenticate_whenLockoutPermanent_returnsLockoutError() throws Exception { + testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_PERMANENT); + } + + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode) throws Exception { // When only biometric is requested, and sensor is strong enough @@ -1510,6 +1526,21 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); } + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + private void testCanAuthenticate_whenLockedOut_returnLockoutError( + @LockoutTracker.LockoutMode int lockoutMode) + throws Exception { + // When only biometric is requested, and sensor is strong enough + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + + when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) + .thenReturn(lockoutMode); + + // Lockout is not considered an error for BiometricManager#canAuthenticate + assertEquals(BiometricManager.BIOMETRIC_ERROR_LOCKOUT, + invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); + } + @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) public void testCanAuthenticate_whenMandatoryBiometricsRequested() @@ -1529,7 +1560,7 @@ public class BiometricServiceTest { when(mTrustManager.isInSignificantPlace()).thenReturn(true); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); } @@ -1572,7 +1603,7 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); - assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS)); when(mTrustManager.isInSignificantPlace()).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index 240da9fe46bd..4c3a233fdd97 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -231,7 +231,7 @@ public class PreAuthInfoTest { @Test @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) - public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + public void testMandatoryBiometricsAndStrongBiometricsStatus_whenRequirementsNotSatisfied() throws Exception { when(mTrustManager.isInSignificantPlace()).thenReturn(true); @@ -246,6 +246,24 @@ public class PreAuthInfoTest { assertThat(preAuthInfo.eligibleSensors).hasSize(1); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable() + throws Exception { + when(mTrustManager.isInSignificantPlace()).thenReturn(true); + + final BiometricSensor sensor = getFaceSensor(); + final PromptInfo promptInfo = new PromptInfo(); + promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS); + final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager); + + assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo( + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE); + assertThat(preAuthInfo.eligibleSensors).hasSize(0); + } + private BiometricSensor getFingerprintSensor() { BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT, TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index cb75e1ab6cce..14cb22d7698e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -26,16 +26,25 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.PromptInfo; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; @Presubmit @SmallTest public class UtilsTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Test public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() { @@ -215,7 +224,8 @@ public class UtilsTest { } @Test - public void testBiometricConstantsConversion() { + @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversionLegacy() { final int[][] testCases = { {BiometricConstants.BIOMETRIC_SUCCESS, BiometricManager.BIOMETRIC_SUCCESS}, @@ -240,6 +250,34 @@ public class UtilsTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testBiometricConstantsConversion() { + final int[][] testCases = { + {BiometricConstants.BIOMETRIC_SUCCESS, + BiometricManager.BIOMETRIC_SUCCESS}, + {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED}, + {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE}, + {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT, + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT, + BiometricManager.BIOMETRIC_ERROR_LOCKOUT}, + {BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE, + BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE} + }; + + for (int i = 0; i < testCases.length; i++) { + assertEquals(testCases[i][1], + Utils.biometricConstantsToBiometricManager(testCases[i][0])); + } + } + + @Test public void testGetAuthenticationTypeForResult_getsCorrectType() { assertEquals(Utils.getAuthenticationTypeForResult( BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), |