[go: nahoru, domu]

Add support for AuthenticationResult#getAuthenticationType()

Updates the AndroidX implementation of BiometricPrompt to add
backwards-compatible support for the
AuthenticationResult#getAuthenticationType() method add in Android 11
(API 30).

Relnote: "Added support for
BiometricPrompt.AuthenticationResult#getAuthenticationType()"

Test: Biometric integration test app on API 27-30.
Test: ./gradlew biometric:biometric:test
Test: ./gradlew biometric:biometric:connectedAndroidTest

Bug: 148223669
Change-Id: Icfad54cdf7baa5719e54464e0d33fc6757bb12c2
diff --git a/biometric/biometric/api/1.1.0-alpha02.txt b/biometric/biometric/api/1.1.0-alpha02.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/1.1.0-alpha02.txt
+++ b/biometric/biometric/api/1.1.0-alpha02.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/api/current.txt b/biometric/biometric/api/current.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/current.txt
+++ b/biometric/biometric/api/current.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/api/public_plus_experimental_1.1.0-alpha02.txt b/biometric/biometric/api/public_plus_experimental_1.1.0-alpha02.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/public_plus_experimental_1.1.0-alpha02.txt
+++ b/biometric/biometric/api/public_plus_experimental_1.1.0-alpha02.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/api/public_plus_experimental_current.txt b/biometric/biometric/api/public_plus_experimental_current.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/public_plus_experimental_current.txt
+++ b/biometric/biometric/api/public_plus_experimental_current.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/api/restricted_1.1.0-alpha02.txt b/biometric/biometric/api/restricted_1.1.0-alpha02.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/restricted_1.1.0-alpha02.txt
+++ b/biometric/biometric/api/restricted_1.1.0-alpha02.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/api/restricted_current.txt b/biometric/biometric/api/restricted_current.txt
index c5d2b64..9a3ee13 100644
--- a/biometric/biometric/api/restricted_current.txt
+++ b/biometric/biometric/api/restricted_current.txt
@@ -28,6 +28,9 @@
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo, androidx.biometric.BiometricPrompt.CryptoObject);
     method public void authenticate(androidx.biometric.BiometricPrompt.PromptInfo);
     method public void cancelAuthentication();
+    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 AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1; // 0xffffffff
     field public static final int ERROR_CANCELED = 5; // 0x5
     field public static final int ERROR_HW_NOT_PRESENT = 12; // 0xc
     field public static final int ERROR_HW_UNAVAILABLE = 1; // 0x1
@@ -51,6 +54,7 @@
   }
 
   public static class BiometricPrompt.AuthenticationResult {
+    method public int getAuthenticationType();
     method public androidx.biometric.BiometricPrompt.CryptoObject? getCryptoObject();
   }
 
diff --git a/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java b/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
index d5dad84..db6e9f0 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
@@ -47,7 +47,7 @@
          * See {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int,
          * CharSequence)}.
          *
-         * @param errorCode An integer ID associated with the error.
+         * @param errorCode    An integer ID associated with the error.
          * @param errorMessage A human-readable message that describes the error.
          */
         void onError(int errorCode, @Nullable CharSequence errorMessage) {}
@@ -104,7 +104,7 @@
      * callback object.
      *
      * @return A callback object that can be passed to
-     *  {@link android.hardware.biometrics.BiometricPrompt}.
+     * {@link android.hardware.biometrics.BiometricPrompt}.
      */
     @RequiresApi(Build.VERSION_CODES.P)
     @NonNull
@@ -123,7 +123,7 @@
      * callback object.
      *
      * @return A callback object that can be passed to
-     *  {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
+     * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
      */
     @NonNull
     androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
@@ -144,13 +144,17 @@
                 @Override
                 public void onAuthenticationSucceeded(final androidx.core.hardware.fingerprint
                         .FingerprintManagerCompat.AuthenticationResult result) {
-                    final BiometricPrompt.AuthenticationResult unwrappedResult =
-                            result != null
-                                    ? new BiometricPrompt.AuthenticationResult(
-                                            CryptoObjectUtils.unwrapFromFingerprintManager(
-                                                    result.getCryptoObject()))
-                                    : new BiometricPrompt.AuthenticationResult(null /* crypto */);
-                    mListener.onSuccess(unwrappedResult);
+
+                    final BiometricPrompt.CryptoObject crypto = result != null
+                            ? CryptoObjectUtils.unwrapFromFingerprintManager(
+                                    result.getCryptoObject())
+                            : null;
+
+                    final BiometricPrompt.AuthenticationResult resultCompat =
+                            new BiometricPrompt.AuthenticationResult(
+                                    crypto, BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+
+                    mListener.onSuccess(resultCompat);
                 }
 
                 @Override
@@ -163,6 +167,30 @@
     }
 
     /**
+     * Nested class to avoid verification errors for methods introduced in Android 11 (API 30).
+     */
+    @RequiresApi(Build.VERSION_CODES.R)
+    private static class Api30Impl {
+        // Prevent instantiation.
+        private Api30Impl() {}
+
+        /**
+         * Gets the authentication type from the given framework authentication result.
+         *
+         * @param result An instance of
+         *               {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}.
+         * @return The value returned by calling {@link
+         * android.hardware.biometrics.BiometricPrompt.AuthenticationResult#getAuthenticationType()}
+         * for the given result object.
+         */
+        @BiometricPrompt.AuthenticationResultType
+        static int getAuthenticationType(
+                @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
+            return result.getAuthenticationType();
+        }
+    }
+
+    /**
      * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
      */
     @RequiresApi(Build.VERSION_CODES.P)
@@ -176,7 +204,7 @@
          *
          * @param listener A listener object that will receive authentication events.
          * @return A new instance of
-         *  {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}.
+         * {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}.
          */
         @NonNull
         static android.hardware.biometrics.BiometricPrompt.AuthenticationCallback createCallback(
@@ -196,13 +224,26 @@
                 @Override
                 public void onAuthenticationSucceeded(
                         android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
-                    final BiometricPrompt.AuthenticationResult unwrappedResult =
-                            new BiometricPrompt.AuthenticationResult(
-                                    result != null
-                                            ? CryptoObjectUtils.unwrapFromBiometricPrompt(
-                                                    result.getCryptoObject())
-                                            : null);
-                    listener.onSuccess(unwrappedResult);
+
+                    final BiometricPrompt.CryptoObject crypto = result != null
+                            ? CryptoObjectUtils.unwrapFromBiometricPrompt(result.getCryptoObject())
+                            : null;
+
+                    @BiometricPrompt.AuthenticationResultType final int authenticationType;
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                        authenticationType = result != null
+                                ? Api30Impl.getAuthenticationType(result)
+                                : BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
+                    } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
+                        authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
+                    } else {
+                        authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+                    }
+
+                    final BiometricPrompt.AuthenticationResult resultCompat =
+                            new BiometricPrompt.AuthenticationResult(crypto, authenticationType);
+
+                    listener.onSuccess(resultCompat);
                 }
 
                 @Override
diff --git a/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java b/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
index db2bbaa..0d380b7 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/AuthenticatorUtils.java
@@ -26,6 +26,12 @@
  * Utilities related to {@link BiometricManager.Authenticators} constants.
  */
 class AuthenticatorUtils {
+    /**
+     * A bitmask for the portion of an {@link BiometricManager.AuthenticatorTypes} value related to
+     * biometric sensor class.
+     */
+    private static final int BIOMETRIC_CLASS_MASK = 0x7FFF;
+
     // Prevent instantiation.
     private AuthenticatorUtils() {}
 
@@ -128,6 +134,16 @@
     }
 
     /**
+     * Checks if any biometric class is included in the given set of allowed authenticator types.
+     *
+     * @param authenticators A bit field representing a set of allowed authenticator types.
+     * @return Whether the allowed authenticator types include one or more biometric classes.
+     */
+    static boolean isSomeBiometricAllowed(@BiometricManager.AuthenticatorTypes int authenticators) {
+        return (authenticators & BIOMETRIC_CLASS_MASK) != 0;
+    }
+
+    /**
      * Checks if a <strong>Class 2</strong> (formerly <strong>Weak</strong>) biometric is included
      * in the given set of allowed authenticator types.
      *
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 60c17c7..51831b0 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -654,9 +654,11 @@
      */
     private void handleConfirmCredentialResult(int resultCode) {
         if (resultCode == Activity.RESULT_OK) {
-            // Device credential auth succeeded. This is incompatible with crypto.
+            // Device credential auth succeeded. This is incompatible with crypto for API <30.
             sendSuccessAndDismiss(
-                    new BiometricPrompt.AuthenticationResult(null /* crypto */));
+                    new BiometricPrompt.AuthenticationResult(
+                            null /* crypto */,
+                            BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL));
         } else {
             // Device credential auth failed. Assume this is due to the user canceling.
             sendErrorAndDismiss(
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index 0d75cca..1ada95a 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -78,6 +78,40 @@
     @interface AuthenticationError {}
 
     /**
+     * Authentication type reported by {@link AuthenticationResult} when the user authenticated via
+     * an unknown method.
+     *
+     * <p>This value may be returned on older Android versions due to partial incompatibility
+     * with a newer API. It does NOT necessarily imply that the user authenticated with a method
+     * other than those represented by {@link #AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL} and
+     * {@link #AUTHENTICATION_RESULT_TYPE_BIOMETRIC}.
+     */
+    public static final int AUTHENTICATION_RESULT_TYPE_UNKNOWN = -1;
+
+    /**
+     * 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;
+
+    /**
+     * The authentication type that was used, as reported by {@link AuthenticationResult}.
+     */
+    @IntDef({
+            AUTHENTICATION_RESULT_TYPE_UNKNOWN,
+            AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL,
+            AUTHENTICATION_RESULT_TYPE_BIOMETRIC
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AuthenticationResultType {}
+
+    /**
      * Tag used to identify the {@link BiometricFragment} attached to the client activity/fragment.
      */
     private static final String BIOMETRIC_FRAGMENT_TAG = "androidx.biometric.BiometricFragment";
@@ -161,9 +195,12 @@
      */
     public static class AuthenticationResult {
         private final CryptoObject mCryptoObject;
+        @AuthenticationResultType private final int mAuthenticationType;
 
-        AuthenticationResult(CryptoObject crypto) {
+        AuthenticationResult(
+                CryptoObject crypto, @AuthenticationResultType int authenticationType) {
             mCryptoObject = crypto;
+            mAuthenticationType = authenticationType;
         }
 
         /**
@@ -175,6 +212,21 @@
         public CryptoObject getCryptoObject() {
             return mCryptoObject;
         }
+
+        /**
+         * Gets the type of authentication (e.g. device credential or biometric) that was
+         * requested from and successfully provided by the user.
+         *
+         * @return An integer representing the type of authentication that was used.
+         *
+         * @see #AUTHENTICATION_RESULT_TYPE_UNKNOWN
+         * @see #AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL
+         * @see #AUTHENTICATION_RESULT_TYPE_BIOMETRIC
+         */
+        @AuthenticationResultType
+        public int getAuthenticationType() {
+            return mAuthenticationType;
+        }
     }
 
     /**
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
index d7fbdee..f403a81 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
@@ -302,6 +302,14 @@
                         @Override
                         void onSuccess(@NonNull BiometricPrompt.AuthenticationResult result) {
                             if (isAwaitingResult()) {
+                                // Try to infer the authentication type if unknown.
+                                if (result.getAuthenticationType()
+                                        == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN) {
+                                    result = new BiometricPrompt.AuthenticationResult(
+                                            result.getCryptoObject(),
+                                            getInferredAuthenticationResultType());
+                                }
+
                                 setAuthenticationResult(result);
                             }
                         }
@@ -544,4 +552,21 @@
             liveData.postValue(value);
         }
     }
+
+    /**
+     * Attempts to infer the type of authenticator that was used to authenticate the user.
+     *
+     * @return The inferred authentication type, or
+     * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_UNKNOWN} if unknown.
+     */
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    @BiometricPrompt.AuthenticationResultType
+    int getInferredAuthenticationResultType() {
+        @BiometricManager.AuthenticatorTypes final int authenticators = getAllowedAuthenticators();
+        if (AuthenticatorUtils.isSomeBiometricAllowed(authenticators)
+                && !AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)) {
+            return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+        }
+        return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
+    }
 }
diff --git a/biometric/biometric/src/test/java/androidx/biometric/AuthenticatorUtilsTest.java b/biometric/biometric/src/test/java/androidx/biometric/AuthenticatorUtilsTest.java
index 0fa80d7..ef9355c 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/AuthenticatorUtilsTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/AuthenticatorUtilsTest.java
@@ -144,6 +144,21 @@
     }
 
     @Test
+    public void testIsSomeBiometricAllowed() {
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(0)).isFalse();
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(Authenticators.BIOMETRIC_STRONG))
+                .isTrue();
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(Authenticators.BIOMETRIC_WEAK))
+                .isTrue();
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(Authenticators.DEVICE_CREDENTIAL))
+                .isFalse();
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(
+                Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL)).isTrue();
+        assertThat(AuthenticatorUtils.isSomeBiometricAllowed(
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL)).isTrue();
+    }
+
+    @Test
     public void testIsWeakBiometricAllowed() {
         assertThat(AuthenticatorUtils.isWeakBiometricAllowed(0)).isFalse();
         assertThat(AuthenticatorUtils.isWeakBiometricAllowed(Authenticators.BIOMETRIC_STRONG))
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
index 7c490e7..df7ede4 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
@@ -84,7 +84,9 @@
         mViewModel.setClientCallback(mAuthenticationCallback);
         mViewModel.setAwaitingResult(true);
 
-        mFragment.onAuthenticationSucceeded(new BiometricPrompt.AuthenticationResult(null));
+        mFragment.onAuthenticationSucceeded(
+                new BiometricPrompt.AuthenticationResult(
+                        null /* crypto */, BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
 
         verify(mAuthenticationCallback).onAuthenticationSucceeded(mResultCaptor.capture());
         assertThat(mResultCaptor.getValue().getCryptoObject()).isNull();
diff --git a/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/BiometricTestActivity.kt b/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/BiometricTestActivity.kt
index 617d86b..f3fe670 100644
--- a/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/BiometricTestActivity.kt
+++ b/biometric/integration-tests/testapp/src/main/java/androidx/biometric/integration/testapp/BiometricTestActivity.kt
@@ -120,7 +120,7 @@
                     result: BiometricPrompt.AuthenticationResult
                 ) {
                     super.onAuthenticationSucceeded(result)
-                    log("onAuthenticationSucceeded: crypto = ${result.cryptoObject}")
+                    log("onAuthenticationSucceeded: ${result.toDataString()}")
 
                     // Encrypt a test payload using the result of crypto-based auth.
                     if (useCryptoAuthCheckbox.isChecked) {
@@ -256,6 +256,14 @@
         private const val PAYLOAD = "hello"
 
         /**
+         * Converts an authentication result object to a string that represents its contents.
+         */
+        private fun BiometricPrompt.AuthenticationResult.toDataString(): String {
+            val typeString = authenticationType.toAuthenticationTypeString()
+            return "crypto = $cryptoObject, type = $typeString"
+        }
+
+        /**
          * Converts an authentication status code to a string that represents the status.
          */
         private fun Int.toAuthenticationStatusString(): String = when (this) {
@@ -267,7 +275,18 @@
             BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> "ERROR_NO_HARDWARE"
             BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED ->
                 "ERROR_SECURITY_UPDATE_REQUIRED"
-            else -> "Unknown error: $this"
+            else -> "Unrecognized error: $this"
+        }
+
+        /**
+         * Converts an authentication result type to a string that represents the method of
+         * authentication.
+         */
+        private fun Int.toAuthenticationTypeString(): String = when (this) {
+            BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN -> "UNKNOWN"
+            BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC -> "BIOMETRIC"
+            BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL -> "DEVICE_CREDENTIAL"
+            else -> "Unrecognized type: $this"
         }
 
         /**