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();
+ }
+ }
+ }
}