diff --git a/api/current.txt b/api/current.txt index ba14b4e6987ad..b5b9984d8409a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16852,6 +16852,8 @@ package android.hardware.biometrics { method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); method public boolean isConfirmationRequired(); + field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2 + field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1 field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0 field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -16881,6 +16883,7 @@ package android.hardware.biometrics { } public static class BiometricPrompt.AuthenticationResult { + method public int getAuthenticationType(); method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject(); } diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java index 698876b9c59ea..11cf2d6987306 100644 --- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java +++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.CancellationSignal; import android.os.Parcelable; @@ -119,6 +120,7 @@ public interface BiometricAuthenticator { class AuthenticationResult { private Identifier mIdentifier; private CryptoObject mCryptoObject; + private @AuthenticationResultType int mAuthenticationType; private int mUserId; /** @@ -129,26 +131,40 @@ public interface BiometricAuthenticator { /** * Authentication result * @param crypto + * @param authenticationType * @param identifier * @param userId * @hide */ - public AuthenticationResult(CryptoObject crypto, Identifier identifier, + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType, Identifier identifier, int userId) { mCryptoObject = crypto; + mAuthenticationType = authenticationType; mIdentifier = identifier; mUserId = userId; } /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link BiometricAuthenticator#authenticate( - * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link BiometricPrompt#authenticate( + * BiometricPrompt.CryptoObject, CancellationSignal, Executor, + * BiometricPrompt.AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return mCryptoObject; } + /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return mAuthenticationType; + } + /** * Obtain the biometric identifier associated with this operation. Applications are strongly * discouraged from associating specific identifiers with specific applications or diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index cb8fc8b1cbb15..a695ce8e511fe 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -40,6 +41,8 @@ import android.util.Log; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.Signature; import java.util.concurrent.Executor; @@ -397,9 +400,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan new IBiometricServiceReceiver.Stub() { @Override - public void onAuthenticationSucceeded() throws RemoteException { + public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) + throws RemoteException { mExecutor.execute(() -> { - final AuthenticationResult result = new AuthenticationResult(mCryptoObject); + final AuthenticationResult result = + new AuthenticationResult(mCryptoObject, authenticationType); mAuthenticationCallback.onAuthenticationSucceeded(result); }); } @@ -576,28 +581,62 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * Container for callback data from {@link #authenticate( CancellationSignal, Executor, + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * entering their device PIN, pattern, or password. + */ + public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; + + /** + * Authentication type reported by {@link AuthenticationResult} when the user authenticated by + * presenting some form of biometric (e.g. fingerprint or face). + */ + public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; + + /** + * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) + public @interface AuthenticationResultType { + } + + /** + * Container for callback data from {@link #authenticate(CancellationSignal, Executor, * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, - * AuthenticationCallback)} + * AuthenticationCallback)}. */ public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { /** * Authentication result * @param crypto + * @param authenticationType * @hide */ - public AuthenticationResult(CryptoObject crypto) { + public AuthenticationResult(CryptoObject crypto, + @AuthenticationResultType int authenticationType) { // Identifier and userId is not used for BiometricPrompt. - super(crypto, null /* identifier */, 0 /* userId */); + super(crypto, authenticationType, null /* identifier */, 0 /* userId */); } + /** - * Obtain the crypto object associated with this transaction - * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, - * Executor, AuthenticationCallback)} + * Provides the crypto object associated with this transaction. + * @return The crypto object provided to {@link #authenticate(CryptoObject, + * CancellationSignal, Executor, AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return (CryptoObject) super.getCryptoObject(); } + + /** + * Provides the type of authentication (e.g. device credential or biometric) that was + * requested from and successfully provided by the user. + * + * @return An integer value representing the authentication method used. + */ + public @AuthenticationResultType int getAuthenticationType() { + return super.getAuthenticationType(); + } } /** diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl index c960049438f1c..1d43aa640b402 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl @@ -20,8 +20,8 @@ package android.hardware.biometrics; * @hide */ oneway interface IBiometricServiceReceiver { - // Notify BiometricPrompt that authentication was successful - void onAuthenticationSucceeded(); + // Notify BiometricPrompt that authentication was successful. + void onAuthenticationSucceeded(int authenticationType); // Noties that authentication failed. void onAuthenticationFailed(); // Notify BiometricPrompt that an error has occurred. diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e1a9f3b97e9ac..8dd3242c947a5 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1400,7 +1400,8 @@ public class BiometricService extends SystemService { if (mCurrentAuthSession.mTokenEscrow != null) { mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); } - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded( + Utils.getAuthenticationTypeForResult(reason)); break; case BiometricPrompt.DISMISSED_REASON_NEGATIVE: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 19f535876274c..389763b5377aa 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; @@ -210,4 +211,30 @@ public class Utils { } return biometricManagerCode; } + + /** + * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of + * granularity supported by {@link BiometricPrompt.AuthenticationResult}. + * + * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of: + * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED}, + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or + * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED} + * @return An integer representing the authentication type for {@link + * BiometricPrompt.AuthenticationResult}. + * @throws IllegalArgumentException if given an invalid dismissal reason. + */ + public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) { + switch (reason) { + case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL; + + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: + return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC; + + default: + throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); + } + } } 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 f96d9961d364b..bec265e6d62d8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -411,7 +411,8 @@ public class BiometricServiceTest { // HAT sent to keystore verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); // Send onAuthenticated to client - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); // Current session becomes null assertNull(mBiometricService.mCurrentAuthSession); } @@ -461,7 +462,8 @@ public class BiometricServiceTest { BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); waitForIdle(); verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); - verify(mReceiver1).onAuthenticationSucceeded(); + verify(mReceiver1).onAuthenticationSucceeded( + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); } @Test 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 abe39f0934db5..312ff2ca84a16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -223,4 +223,22 @@ public class UtilsTest { Utils.biometricConstantsToBiometricManager(testCases[i][0])); } } + + @Test + public void testGetAuthenticationTypeForResult_getsCorrectType() { + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + assertEquals(Utils.getAuthenticationTypeForResult( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED), + BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetAuthResultType_throwsForInvalidReason() { + Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + } }