10/n Prevent androidx.biometric verification errors

Moves all SDK-gated method calls to nested static Api##Impl classes to
avoid class verification issues on older Android versions.

Test: Biometric support demo app on API 27-29.
Test: adb logcat | grep 'androidx.biometric'

Bug: 152073877
Change-Id: I5d92e2b7a1eebb24c977fe962859e6024c82f74f
diff --git a/biometric/biometric/proguard-rules.pro b/biometric/biometric/proguard-rules.pro
new file mode 100644
index 0000000..f80238f
--- /dev/null
+++ b/biometric/biometric/proguard-rules.pro
@@ -0,0 +1,39 @@
+# Copyright (C) 2020 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.
+
+# Never inline methods, but allow shrinking and obfuscation.
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.AuthenticationCallbackProvider$Api* {
+    <methods>;
+}
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.BiometricFragment$Api* {
+    <methods>;
+}
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.BiometricManager$Api* {
+    <methods>;
+}
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.CancellationSignalProvider$Api* {
+    <methods>;
+}
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.CryptoObjectUtils$Api* {
+    <methods>;
+}
+-keepclassmembernames,allowobfuscation,allowshrinking
+        class androidx.biometric.FingerprintDialogFragment$Api* {
+    <methods>;
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java b/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
index 5b945a0..e138183 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/AuthenticationCallbackProvider.java
@@ -108,38 +108,9 @@
      */
     @RequiresApi(Build.VERSION_CODES.P)
     @NonNull
-    android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
-            getBiometricCallback() {
+    android.hardware.biometrics.BiometricPrompt.AuthenticationCallback getBiometricCallback() {
         if (mBiometricCallback == null) {
-            mBiometricCallback = new android.hardware.biometrics.BiometricPrompt
-                    .AuthenticationCallback() {
-                @Override
-                public void onAuthenticationError(int errorCode, CharSequence errString) {
-                    mListener.onError(errorCode, errString);
-                }
-
-                @Override
-                public void onAuthenticationHelp(
-                        final int helpCode, final CharSequence helpString) {
-                    // Don't forward the result to the client, since the dialog takes care of it.
-                }
-
-                @Override
-                public void onAuthenticationSucceeded(
-                        android.hardware.biometrics.BiometricPrompt.AuthenticationResult result) {
-                    final BiometricPrompt.AuthenticationResult unwrappedResult =
-                            new BiometricPrompt.AuthenticationResult(result != null
-                                    ? CryptoObjectUtils.unwrapFromBiometricPrompt(
-                                            result.getCryptoObject())
-                                    : null);
-                    mListener.onSuccess(unwrappedResult);
-                }
-
-                @Override
-                public void onAuthenticationFailed() {
-                    mListener.onFailure();
-                }
-            };
+            mBiometricCallback = Api28Impl.createCallback(mListener);
         }
         return mBiometricCallback;
     }
@@ -190,4 +161,52 @@
         }
         return mFingerprintCallback;
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
+     */
+    @RequiresApi(Build.VERSION_CODES.P)
+    private static class Api28Impl {
+        /**
+         * Creates a {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} that
+         * delegates events to the given listener.
+         *
+         * @param listener A listener object that will receive authentication events.
+         * @return A new instance of
+         *  {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}.
+         */
+        @NonNull
+        static android.hardware.biometrics.BiometricPrompt.AuthenticationCallback createCallback(
+                @NonNull final Listener listener) {
+            return new android.hardware.biometrics.BiometricPrompt.AuthenticationCallback() {
+                @Override
+                public void onAuthenticationError(int errorCode, CharSequence errString) {
+                    listener.onError(errorCode, errString);
+                }
+
+                @Override
+                public void onAuthenticationHelp(
+                        final int helpCode, final CharSequence helpString) {
+                    // Don't forward the result to the client, since the dialog takes care of it.
+                }
+
+                @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);
+                }
+
+                @Override
+                public void onAuthenticationFailed() {
+                    listener.onFailure();
+                }
+            };
+        }
+    }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 7b336e1..034bdde 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
@@ -281,9 +282,8 @@
         }
 
         // Fall back to device credential immediately if no biometrics are enrolled.
-        if (mViewModel.isDeviceCredentialAllowed()
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
-                && Build.VERSION.SDK_INT <= Build.VERSION_CODES.P
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+                && isManagingDeviceCredentialButton()
                 && BiometricManager.from(activity).canAuthenticate()
                         != BiometricManager.BIOMETRIC_SUCCESS) {
             mViewModel.setAwaitingResult(true);
@@ -355,28 +355,28 @@
      * begins authentication.
      */
     @RequiresApi(Build.VERSION_CODES.P)
-    @SuppressWarnings("deprecation")
     private void showBiometricPromptForAuthentication() {
         final Context context = requireContext();
         final android.hardware.biometrics.BiometricPrompt.Builder builder =
-                new android.hardware.biometrics.BiometricPrompt.Builder(context);
+                Api28Impl.createPromptBuilder(context);
 
         final CharSequence title = mViewModel.getTitle();
         final CharSequence subtitle = mViewModel.getSubtitle();
         final CharSequence description = mViewModel.getDescription();
         if (title != null) {
-            builder.setTitle(title);
+            Api28Impl.setTitle(builder, title);
         }
         if (subtitle != null) {
-            builder.setSubtitle(subtitle);
+            Api28Impl.setSubtitle(builder, subtitle);
         }
         if (description != null) {
-            builder.setDescription(description);
+            Api28Impl.setDescription(builder, description);
         }
 
         final CharSequence negativeButtonText = mViewModel.getNegativeButtonText();
         if (!TextUtils.isEmpty(negativeButtonText)) {
-            builder.setNegativeButton(
+            Api28Impl.setNegativeButton(
+                    builder,
                     negativeButtonText,
                     mViewModel.getClientExecutor(),
                     mViewModel.getNegativeButtonListener());
@@ -384,24 +384,29 @@
 
         // Set builder flags introduced in Q.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            builder.setConfirmationRequired(mViewModel.isConfirmationRequired());
-            builder.setDeviceCredentialAllowed(mViewModel.isDeviceCredentialAllowed());
+            Api29Impl.setConfirmationRequired(builder, mViewModel.isConfirmationRequired());
+            Api29Impl.setDeviceCredentialAllowed(builder, mViewModel.isDeviceCredentialAllowed());
         }
 
-        final android.hardware.biometrics.BiometricPrompt biometricPrompt = builder.build();
+        final android.hardware.biometrics.BiometricPrompt biometricPrompt =
+                Api28Impl.buildPrompt(builder);
         final android.os.CancellationSignal cancellationSignal =
                 mViewModel.getCancellationSignalProvider().getBiometricCancellationSignal();
         final android.hardware.biometrics.BiometricPrompt.AuthenticationCallback callback =
                 mViewModel.getAuthenticationCallbackProvider().getBiometricCallback();
         final BiometricPrompt.CryptoObject cryptoObject = mViewModel.getCryptoObject();
         if (mViewModel.getCryptoObject() == null) {
-            biometricPrompt.authenticate(cancellationSignal, mPromptExecutor, callback);
+            Api28Impl.authenticate(biometricPrompt, cancellationSignal, mPromptExecutor, callback);
         } else {
             android.hardware.biometrics.BiometricPrompt.CryptoObject wrappedCryptoObject =
                     Objects.requireNonNull(
                             CryptoObjectUtils.wrapForBiometricPrompt(cryptoObject));
-            biometricPrompt.authenticate(
-                    wrappedCryptoObject, cancellationSignal, mPromptExecutor, callback);
+            Api28Impl.authenticate(
+                    biometricPrompt,
+                    wrappedCryptoObject,
+                    cancellationSignal,
+                    mPromptExecutor,
+                    callback);
         }
     }
 
@@ -590,7 +595,7 @@
         // Get the KeyguardManager service in whichever way the platform supports.
         final KeyguardManager keyguardManager;
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            keyguardManager = activity.getSystemService(KeyguardManager.class);
+            keyguardManager = Api23Impl.getKeyguardManager(activity);
         } else {
             final Object service = activity.getSystemService(Context.KEYGUARD_SERVICE);
             keyguardManager = service instanceof KeyguardManager ? (KeyguardManager) service : null;
@@ -608,9 +613,8 @@
         final CharSequence description = mViewModel.getDescription();
         final CharSequence credentialDescription = subtitle != null ? subtitle : description;
 
-        @SuppressWarnings("deprecation")
-        final Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(
-                title, credentialDescription);
+        final Intent intent = Api21Impl.createConfirmDeviceCredentialIntent(
+                keyguardManager, title, credentialDescription);
 
         if (intent == null) {
             Log.e(TAG, "Failed to check device credential. Got null intent from Keyguard.");
@@ -842,4 +846,205 @@
                 ? 0
                 : HIDE_DIALOG_DELAY_MS;
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 10 (API 29).
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private static class Api29Impl {
+        /**
+         * Sets the "confirmation required" option for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param confirmationRequired The value for the "confirmation required" option.
+         */
+        static void setConfirmationRequired(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                boolean confirmationRequired) {
+            builder.setConfirmationRequired(confirmationRequired);
+        }
+
+        /**
+         * Sets the "device credential allowed" option for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param deviceCredentialAllowed The value for the "device credential allowed" option.
+         */
+        @SuppressWarnings("deprecation")
+        static void setDeviceCredentialAllowed(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                boolean deviceCredentialAllowed) {
+            builder.setDeviceCredentialAllowed(deviceCredentialAllowed);
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
+     */
+    @RequiresApi(Build.VERSION_CODES.P)
+    private static class Api28Impl {
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         *
+         * @param context The application or activity context.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         */
+        @NonNull
+        static android.hardware.biometrics.BiometricPrompt.Builder createPromptBuilder(
+                @NonNull Context context) {
+            return new android.hardware.biometrics.BiometricPrompt.Builder(context);
+        }
+
+        /**
+         * Sets the title for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param title The title for the prompt.
+         */
+        static void setTitle(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                @NonNull CharSequence title) {
+            builder.setTitle(title);
+        }
+
+        /**
+         * Sets the subtitle for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param subtitle The subtitle for the prompt.
+         */
+        static void setSubtitle(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                @NonNull CharSequence subtitle) {
+            builder.setSubtitle(subtitle);
+        }
+
+        /**
+         * Sets the description for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param description The description for the prompt.
+         */
+        static void setDescription(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                @NonNull CharSequence description) {
+            builder.setDescription(description);
+        }
+
+        /**
+         * Sets the negative button text and behavior for the given framework prompt builder.
+         *
+         * @param builder An instance of
+         *                {@link android.hardware.biometrics.BiometricPrompt.Builder}.
+         * @param text The text for the negative button.
+         * @param executor An executor for the negative button callback.
+         * @param listener A listener for the negative button press event.
+         */
+        static void setNegativeButton(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder,
+                @NonNull CharSequence text,
+                @NonNull Executor executor,
+                @NonNull DialogInterface.OnClickListener listener) {
+            builder.setNegativeButton(text, executor, listener);
+        }
+
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt} from the given builder.
+         *
+         * @param builder The builder for the prompt.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt}.
+         */
+        @NonNull
+        static android.hardware.biometrics.BiometricPrompt buildPrompt(
+                @NonNull android.hardware.biometrics.BiometricPrompt.Builder builder) {
+            return builder.build();
+        }
+
+        /**
+         * Starts (non-crypto) authentication for the given framework biometric prompt.
+         *
+         * @param biometricPrompt An instance of
+         *                        {@link android.hardware.biometrics.BiometricPrompt}.
+         * @param cancellationSignal A cancellation signal object for the prompt.
+         * @param executor An executor for authentication callbacks.
+         * @param callback An object that will receive authentication events.
+         */
+        static void authenticate(
+                @NonNull android.hardware.biometrics.BiometricPrompt biometricPrompt,
+                @NonNull android.os.CancellationSignal cancellationSignal,
+                @NonNull Executor executor,
+                @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
+                        callback) {
+            biometricPrompt.authenticate(cancellationSignal, executor, callback);
+        }
+
+        /**
+         * Starts (crypto-based) authentication for the given framework biometric prompt.
+         *
+         * @param biometricPrompt An instance of
+         *                        {@link android.hardware.biometrics.BiometricPrompt}.
+         * @param crypto A crypto object associated with the given authentication.
+         * @param cancellationSignal A cancellation signal object for the prompt.
+         * @param executor An executor for authentication callbacks.
+         * @param callback An object that will receive authentication events.
+         */
+        static void authenticate(
+                @NonNull android.hardware.biometrics.BiometricPrompt biometricPrompt,
+                @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto,
+                @NonNull android.os.CancellationSignal cancellationSignal,
+                @NonNull Executor executor,
+                @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
+                        callback) {
+            biometricPrompt.authenticate(crypto, cancellationSignal, executor, callback);
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 6.0 (API 23).
+     */
+    @RequiresApi(Build.VERSION_CODES.M)
+    private static class Api23Impl {
+        /**
+         * Gets an instance of the {@link KeyguardManager} system service.
+         *
+         * @param context The application or activity context.
+         * @return An instance of {@link KeyguardManager}.
+         */
+        @Nullable
+        static KeyguardManager getKeyguardManager(@NonNull Context context) {
+            return context.getSystemService(KeyguardManager.class);
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 5.0 (API 21).
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    private static class Api21Impl {
+        /**
+         * Calls
+         * {@link KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}
+         * for the given keyguard manager.
+         *
+         * @param keyguardManager An instance of {@link KeyguardManager}.
+         * @param title The title for the confirm device credential activity.
+         * @param description The description for the confirm device credential activity.
+         * @return An intent that can be used to launch the confirm device credential activity.
+         */
+        @SuppressWarnings("deprecation")
+        @Nullable
+        static Intent createConfirmDeviceCredentialIntent(
+                @NonNull KeyguardManager keyguardManager,
+                @Nullable CharSequence title,
+                @Nullable CharSequence description) {
+            return keyguardManager.createConfirmDeviceCredentialIntent(title, description);
+        }
+    }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
index a65d940..467f4a7 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricManager.java
@@ -47,8 +47,7 @@
     private final androidx.core.hardware.fingerprint.FingerprintManagerCompat mFingerprintManager;
 
     // Only guaranteed to be non-null on API 29+.
-    @Nullable
-    private final android.hardware.biometrics.BiometricManager mBiometricManager;
+    @Nullable private final android.hardware.biometrics.BiometricManager mBiometricManager;
 
     /**
      * No error detected.
@@ -86,19 +85,6 @@
     @Retention(RetentionPolicy.SOURCE)
     private @interface BiometricError {}
 
-    @RequiresApi(Build.VERSION_CODES.Q)
-    private static class Api29Impl {
-        @NonNull
-        static android.hardware.biometrics.BiometricManager create(Context context) {
-            return context.getSystemService(android.hardware.biometrics.BiometricManager.class);
-        }
-
-        static @BiometricError int canAuthenticate(
-                android.hardware.biometrics.BiometricManager biometricManager) {
-            return biometricManager.canAuthenticate();
-        }
-    }
-
     /**
      * Creates a {@link BiometricManager} instance from the given context.
      *
@@ -110,7 +96,7 @@
     }
 
     // Prevent direct instantiation.
-    private BiometricManager(Context context) {
+    private BiometricManager(@NonNull Context context) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
             mBiometricManager = Api29Impl.create(context);
             mFingerprintManager = null;
@@ -162,4 +148,37 @@
             }
         }
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 10 (API 29).
+     */
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private static class Api29Impl {
+        /**
+         * Gets an instance of the framework
+         * {@link android.hardware.biometrics.BiometricManager} class.
+         *
+         * @param context The application or activity context.
+         * @return An instance of {@link android.hardware.biometrics.BiometricManager}.
+         */
+        @Nullable
+        static android.hardware.biometrics.BiometricManager create(@NonNull Context context) {
+            return context.getSystemService(android.hardware.biometrics.BiometricManager.class);
+        }
+
+        /**
+         * Calls {@link android.hardware.biometrics.BiometricManager#canAuthenticate()} for the
+         * given biometric manager.
+         *
+         * @param biometricManager An instance of
+         *  {@link android.hardware.biometrics.BiometricManager}.
+         * @return The result of
+         *  {@link android.hardware.biometrics.BiometricManager#canAuthenticate()}.
+         */
+        @BiometricError
+        static int canAuthenticate(
+                android.hardware.biometrics.BiometricManager biometricManager) {
+            return biometricManager.canAuthenticate();
+        }
+    }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java b/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
index 429031c..63c977c 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
@@ -55,7 +55,7 @@
     @NonNull
     android.os.CancellationSignal getBiometricCancellationSignal() {
         if (mBiometricCancellationSignal == null) {
-            mBiometricCancellationSignal = new android.os.CancellationSignal();
+            mBiometricCancellationSignal = Api16Impl.create();
         }
         return mBiometricCancellationSignal;
     }
@@ -84,7 +84,7 @@
     void cancel() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
                 && mBiometricCancellationSignal != null) {
-            mBiometricCancellationSignal.cancel();
+            Api16Impl.cancel(mBiometricCancellationSignal);
             mBiometricCancellationSignal = null;
         }
         if (mFingerprintCancellationSignal != null) {
@@ -92,4 +92,26 @@
             mFingerprintCancellationSignal = null;
         }
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 4.1 (API 16).
+     */
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+    private static class Api16Impl {
+        /**
+         * Creates a new instance of the platform class {@link android.os.CancellationSignal}.
+         *
+         * @return An instance of {@link android.os.CancellationSignal}.
+         */
+        static android.os.CancellationSignal create() {
+            return new android.os.CancellationSignal();
+        }
+
+        /**
+         * Calls {@link android.os.CancellationSignal#cancel()} for the given cancellation signal.
+         */
+        static void cancel(android.os.CancellationSignal cancellationSignal) {
+            cancellationSignal.cancel();
+        }
+    }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java b/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
index a27913c..f8527cb 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
@@ -18,9 +18,15 @@
 
 import android.os.Build;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
 /**
  * Utility class for converting between different types of crypto objects that may be used
  * internally by {@link BiometricPrompt} and {@link BiometricManager}.
@@ -35,20 +41,31 @@
      * @param cryptoObject A crypto object from {@link android.hardware.biometrics.BiometricPrompt}.
      * @return An equivalent {@link androidx.biometric.BiometricPrompt.CryptoObject} instance.
      */
-    @RequiresApi(api = Build.VERSION_CODES.P)
-    static @Nullable BiometricPrompt.CryptoObject unwrapFromBiometricPrompt(
+    @RequiresApi(Build.VERSION_CODES.P)
+    @Nullable
+    static BiometricPrompt.CryptoObject unwrapFromBiometricPrompt(
             @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject cryptoObject) {
+
         if (cryptoObject == null) {
             return null;
-        } else if (cryptoObject.getCipher() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getCipher());
-        } else if (cryptoObject.getSignature() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getSignature());
-        } else if (cryptoObject.getMac() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getMac());
-        } else {
-            return null;
         }
+
+        final Cipher cipher = Api28Impl.getCipher(cryptoObject);
+        if (cipher != null) {
+            return new BiometricPrompt.CryptoObject(cipher);
+        }
+
+        final Signature signature = Api28Impl.getSignature(cryptoObject);
+        if (signature != null) {
+            return new BiometricPrompt.CryptoObject(signature);
+        }
+
+        final Mac mac = Api28Impl.getMac(cryptoObject);
+        if (mac != null) {
+            return new BiometricPrompt.CryptoObject(mac);
+        }
+
+        return null;
     }
 
     /**
@@ -58,23 +75,31 @@
      * @return An equivalent crypto object that is compatible with
      *  {@link android.hardware.biometrics.BiometricPrompt}.
      */
-    @RequiresApi(api = Build.VERSION_CODES.P)
-    static @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject
+    @RequiresApi(Build.VERSION_CODES.P)
+    @Nullable
+    static android.hardware.biometrics.BiometricPrompt.CryptoObject
             wrapForBiometricPrompt(@Nullable BiometricPrompt.CryptoObject cryptoObject) {
+
         if (cryptoObject == null) {
             return null;
-        } else if (cryptoObject.getCipher() != null) {
-            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(
-                    cryptoObject.getCipher());
-        } else if (cryptoObject.getSignature() != null) {
-            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(
-                    cryptoObject.getSignature());
-        } else if (cryptoObject.getMac() != null) {
-            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(
-                    cryptoObject.getMac());
-        } else {
-            return null;
         }
+
+        final Cipher cipher = cryptoObject.getCipher();
+        if (cipher != null) {
+            return Api28Impl.create(cipher);
+        }
+
+        final Signature signature = cryptoObject.getSignature();
+        if (signature != null) {
+            return Api28Impl.create(signature);
+        }
+
+        final Mac mac = cryptoObject.getMac();
+        if (mac != null) {
+            return Api28Impl.create(mac);
+        }
+
+        return null;
     }
 
     /**
@@ -90,17 +115,27 @@
     static BiometricPrompt.CryptoObject unwrapFromFingerprintManager(
             @Nullable androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject
                     cryptoObject) {
+
         if (cryptoObject == null) {
             return null;
-        } else if (cryptoObject.getCipher() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getCipher());
-        } else if (cryptoObject.getSignature() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getSignature());
-        } else if (cryptoObject.getMac() != null) {
-            return new BiometricPrompt.CryptoObject(cryptoObject.getMac());
-        } else {
-            return null;
         }
+
+        final Cipher cipher = cryptoObject.getCipher();
+        if (cipher != null) {
+            return new BiometricPrompt.CryptoObject(cipher);
+        }
+
+        final Signature signature = cryptoObject.getSignature();
+        if (signature != null) {
+            return new BiometricPrompt.CryptoObject(signature);
+        }
+
+        final Mac mac = cryptoObject.getMac();
+        if (mac != null) {
+            return new BiometricPrompt.CryptoObject(mac);
+        }
+
+        return null;
     }
 
     /**
@@ -115,19 +150,110 @@
     @Nullable
     static androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject
             wrapForFingerprintManager(@Nullable BiometricPrompt.CryptoObject cryptoObject) {
+
         if (cryptoObject == null) {
             return null;
-        } else if (cryptoObject.getCipher() != null) {
+        }
+
+        final Cipher cipher = cryptoObject.getCipher();
+        if (cipher != null) {
             return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject(
-                    cryptoObject.getCipher());
-        } else if (cryptoObject.getSignature() != null) {
+                    cipher);
+        }
+
+        final Signature signature = cryptoObject.getSignature();
+        if (signature != null) {
             return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject(
-                    cryptoObject.getSignature());
-        } else if (cryptoObject.getMac() != null) {
+                    signature);
+        }
+
+        final Mac mac = cryptoObject.getMac();
+        if (mac != null) {
             return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject(
-                    cryptoObject.getMac());
-        } else {
-            return null;
+                    mac);
+        }
+
+        return null;
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
+     */
+    @RequiresApi(Build.VERSION_CODES.P)
+    private static class Api28Impl {
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given cipher.
+         *
+         * @param cipher The cipher object to be wrapped.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         */
+        static android.hardware.biometrics.BiometricPrompt.CryptoObject create(
+                @NonNull Cipher cipher) {
+            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(cipher);
+        }
+
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given
+         * signature.
+         *
+         * @param signature The signature object to be wrapped.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         */
+        static android.hardware.biometrics.BiometricPrompt.CryptoObject create(
+                @NonNull Signature signature) {
+            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(signature);
+        }
+
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given MAC.
+         *
+         * @param mac The MAC object to be wrapped.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         */
+        static android.hardware.biometrics.BiometricPrompt.CryptoObject create(@NonNull Mac mac) {
+            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(mac);
+        }
+
+        /**
+         * Gets the cipher associated with the given crypto object, if any.
+         *
+         * @param crypto An instance of
+         *               {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         * @return The wrapped cipher object, or {@code null}.
+         */
+        @Nullable
+        static Cipher getCipher(
+                @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) {
+            return crypto.getCipher();
+        }
+
+        /**
+         * Gets the signature associated with the given crypto object, if any.
+         *
+         * @param crypto An instance of
+         *               {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         * @return The wrapped signature object, or {@code null}.
+         */
+        @Nullable
+        static Signature getSignature(
+                @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) {
+            return crypto.getSignature();
+        }
+
+        /**
+         * Gets the MAC associated with the given crypto object, if any.
+         *
+         * @param crypto An instance of
+         *               {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         * @return The wrapped MAC object, or {@code null}.
+         */
+        @Nullable
+        static Mac getMac(
+                @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) {
+            return crypto.getMac();
         }
     }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java b/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
index 0ce0d12..dc763d2 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
@@ -157,7 +157,7 @@
         connectViewModel();
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            mErrorTextColor = getThemedColorFor(android.R.attr.colorError);
+            mErrorTextColor = getThemedColorFor(Api26Impl.getColorErrorAttr());
         } else {
             final Context context = getContext();
             mErrorTextColor = context != null
@@ -294,13 +294,9 @@
                 return;
             }
 
-            final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
-                    ? (AnimatedVectorDrawable) icon
-                    : null;
-
             mFingerprintIcon.setImageDrawable(icon);
-            if (animation != null && shouldAnimateForTransition(previousState, state)) {
-                animation.start();
+            if (shouldAnimateForTransition(previousState, state)) {
+                Api21Impl.startAnimation(icon);
             }
 
             mViewModel.setFingerprintDialogPreviousState(state);
@@ -400,7 +396,6 @@
      * @param state The new state for the fingerprint dialog.
      * @return A drawable asset to be used for the fingerprint icon.
      */
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     private Drawable getAnimationForTransition(@State int previousState, @State int state) {
         final Context context = getContext();
         if (context == null) {
@@ -425,4 +420,34 @@
 
         return ContextCompat.getDrawable(context, iconRes);
     }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 8.0 (API 26).
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    private static final class Api26Impl {
+        /**
+         * Gets the resource ID of the {@code colorError} style attribute.
+         */
+        static int getColorErrorAttr() {
+            return R.attr.colorError;
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 5.0 (API 21).
+     */
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    private static final class Api21Impl {
+        /**
+         * Starts animating the given icon if it is an {@link AnimatedVectorDrawable}.
+         *
+         * @param icon A {@link Drawable} icon asset.
+         */
+        static void startAnimation(@NonNull Drawable icon) {
+            if (icon instanceof AnimatedVectorDrawable) {
+                ((AnimatedVectorDrawable) icon).start();
+            }
+        }
+    }
 }