| /* |
| * Copyright 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. |
| */ |
| |
| package androidx.biometric.auth; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.biometric.BiometricManager; |
| import androidx.biometric.BiometricPrompt; |
| import androidx.biometric.BiometricViewModel; |
| import androidx.lifecycle.ViewModelProvider; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * This class is used to build and configure a {@link BiometricPrompt} for authentication that |
| * permits Class 3 biometric modalities (fingerprint, iris, face, etc), or device credential |
| * modalities (device PIN, pattern, or password), and then start authentication. |
| * |
| * Class 3 (formerly known as Strong) refers to the strength of the biometric sensor, as specified |
| * in the Android 11 CDD. Class 3 authentication can be used for applications that use |
| * cryptographic operations. |
| */ |
| public class Class3BiometricOrCredentialAuthPrompt { |
| |
| /** |
| * The default executor provided when not provided in the |
| * {@link Class3BiometricOrCredentialAuthPrompt} constructor. |
| */ |
| private static class DefaultExecutor implements Executor { |
| private final Handler mHandler = new Handler(Looper.getMainLooper()); |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| DefaultExecutor() {} |
| |
| @Override |
| public void execute(Runnable runnable) { |
| mHandler.post(runnable); |
| } |
| } |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| @NonNull BiometricPrompt mBiometricPrompt; |
| @NonNull private BiometricPrompt.PromptInfo mPromptInfo; |
| private boolean mIsConfirmationRequired; |
| |
| @Nullable private BiometricPrompt.CryptoObject mCrypto; |
| @Nullable private CharSequence mSubtitle; |
| @Nullable private CharSequence mDescription; |
| |
| /** |
| * Constructs a {@link Class3BiometricOrCredentialAuthPrompt}, which can be used to begin |
| * authentication. |
| * @param biometricPrompt Manages a system-provided biometric prompt for authentication |
| * @param promptInfo A set of configurable options for how the {@link BiometricPrompt} |
| * should appear and behave. |
| * @param crypto A crypto object to be associated with this authentication. |
| * @param subtitle The subtitle to be displayed on the prompt. |
| * @param description The description to be displayed on the prompt. |
| * @param confirmationRequired Whether explicit user confirmation is required after a |
| * passive biometric |
| */ |
| Class3BiometricOrCredentialAuthPrompt(@NonNull BiometricPrompt biometricPrompt, |
| @NonNull BiometricPrompt.PromptInfo promptInfo, |
| @Nullable BiometricPrompt.CryptoObject crypto, |
| @NonNull CharSequence subtitle, |
| @NonNull CharSequence description, |
| boolean confirmationRequired) { |
| mBiometricPrompt = biometricPrompt; |
| mPromptInfo = promptInfo; |
| mCrypto = crypto; |
| mSubtitle = subtitle; |
| mDescription = description; |
| mIsConfirmationRequired = confirmationRequired; |
| } |
| |
| /** |
| * Begins authentication using the configured biometric prompt, and returns an |
| * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the biometric |
| * prompt. |
| * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the |
| * biometric prompt using {@link AuthPrompt#cancelAuthentication()} |
| */ |
| @NonNull |
| public AuthPrompt startAuthentication() { |
| if (mCrypto == null) { |
| mBiometricPrompt.authenticate(mPromptInfo); |
| } else { |
| mBiometricPrompt.authenticate(mPromptInfo, mCrypto); |
| } |
| |
| return new AuthPrompt() { |
| @Override |
| public void cancelAuthentication() { |
| mBiometricPrompt.cancelAuthentication(); |
| } |
| }; |
| } |
| |
| /** |
| * Gets the subtitle for the prompt. |
| * @return subtitle for the prompt |
| */ |
| @Nullable |
| public CharSequence getSubtitle() { |
| return mSubtitle; |
| } |
| |
| /** |
| * Gets the description for the prompt. Defaults to null. |
| * @return description for the prompt |
| */ |
| @Nullable |
| public CharSequence getDescription() { |
| return mDescription; |
| } |
| |
| /** |
| * Indicates whether prompt requires explicit user confirmation after a passive biometric (e.g. |
| * iris or face) has been recognized but before {@link |
| * AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity, |
| * BiometricPrompt.AuthenticationResult)} is called. |
| * @return whether prompt requires explicit user confirmation after a passive biometric. |
| */ |
| public boolean isConfirmationRequired() { |
| return mIsConfirmationRequired; |
| } |
| |
| /** |
| * Gets the crypto object for the prompt authentication. |
| * @return Crypto object associated with this authentication. |
| */ |
| @Nullable |
| public BiometricPrompt.CryptoObject getCrypto() { |
| return mCrypto; |
| } |
| |
| /** |
| * Builder to configure a {@link BiometricPrompt} object for class 3 biometric or device |
| * credential authentication with specified options. |
| */ |
| public static final class Builder { |
| // Nullable options on the builder |
| @Nullable private BiometricPrompt.CryptoObject mCrypto = null; |
| @Nullable private CharSequence mSubtitle = null; |
| @Nullable private CharSequence mDescription = null; |
| |
| // Non-null options on the builder |
| @NonNull private final AuthPromptHost mAuthPromptHost; |
| @NonNull private final CharSequence mTitle; |
| @NonNull private final Executor mClientExecutor; |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| @NonNull final AuthPromptCallback mClientCallback; |
| |
| private boolean mIsConfirmationRequired = true; |
| |
| /** |
| * A builder used to set individual options for the |
| * {@link Class3BiometricOrCredentialAuthPrompt} class to construct a |
| * {@link BiometricPrompt} for class 3 biometric or device credential authentication. |
| * |
| * @param authPromptHost {@link androidx.fragment.app.Fragment} or |
| * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt |
| * @param title The title to be displayed on the prompt. |
| * @param clientExecutor The executor that will run authentication callback methods. |
| * @param clientCallback The object that will receive and process authentication events. |
| */ |
| public Builder( |
| @NonNull AuthPromptHost authPromptHost, |
| @NonNull CharSequence title, |
| @NonNull Executor clientExecutor, |
| @NonNull AuthPromptCallback clientCallback) { |
| mAuthPromptHost = authPromptHost; |
| mTitle = title; |
| mClientExecutor = clientExecutor; |
| mClientCallback = clientCallback; |
| } |
| |
| /** |
| * A builder used to set individual options for the |
| * {@link Class3BiometricOrCredentialAuthPrompt} class to construct a |
| * {@link BiometricPrompt} for class 3 biometric or device credential authentication. |
| * Sets mClientExecutor to new DefaultExecutor() object. |
| * |
| * @param authPromptHost {@link androidx.fragment.app.Fragment} or |
| * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt |
| * @param title The title to be displayed on the prompt. |
| * @param clientCallback The object that will receive and process authentication events. |
| */ |
| public Builder( |
| @NonNull AuthPromptHost authPromptHost, |
| @NonNull CharSequence title, |
| @NonNull AuthPromptCallback clientCallback) { |
| mAuthPromptHost = authPromptHost; |
| mTitle = title; |
| mClientExecutor = new DefaultExecutor(); |
| mClientCallback = clientCallback; |
| } |
| |
| /** |
| * Optional: Sets the crypto object for the prompt. |
| * @param crypto A crypto object to be associated with this authentication. |
| */ |
| @NonNull |
| public Builder setCrypto(@NonNull BiometricPrompt.CryptoObject crypto) { |
| mCrypto = crypto; |
| return this; |
| } |
| |
| /** |
| * Optional: Sets the subtitle for the prompt. |
| * |
| * @param subtitle The subtitle to be displayed on the prompt. |
| * @return This builder. |
| */ |
| @NonNull |
| public Builder setSubtitle( |
| @NonNull CharSequence subtitle) { |
| mSubtitle = subtitle; |
| return this; |
| } |
| |
| /** |
| * Optional: Sets the description for the prompt. |
| * |
| * @param description The description to be displayed on the prompt. |
| * @return This builder. |
| */ |
| @NonNull |
| public Builder setDescription( |
| @NonNull CharSequence description) { |
| mDescription = description; |
| return this; |
| } |
| |
| /** |
| * Optional: Sets a system hint for whether to require explicit user confirmation after |
| * a passive biometric (e.g. iris or face) has been recognized but before |
| * {@link AuthPromptCallback#onAuthenticationSucceeded( |
| * androidx.fragment.app.FragmentActivity, BiometricPrompt.AuthenticationResult)} is |
| * called. Defaults to {@code true}. |
| * |
| * <p>Disabling this option is generally only appropriate for frequent, low-value |
| * transactions, such as re-authenticating for a previously authorized application. |
| * |
| * <p>Also note that, as it is merely a hint, this option may be ignored by the system. |
| * For example, the system may choose to instead always require confirmation if the user |
| * has disabled passive authentication for their device in Settings. Additionally, this |
| * option will be ignored on devices running OS versions prior to Android 10 (API 29). |
| * |
| * @param confirmationRequired Whether this option should be enabled. |
| * @return This builder. |
| */ |
| @NonNull |
| public Builder setConfirmationRequired( |
| boolean confirmationRequired) { |
| mIsConfirmationRequired = confirmationRequired; |
| return this; |
| } |
| |
| /** |
| * Configures a {@link BiometricPrompt} object with the specified options, and returns a |
| * {@link Class3BiometricOrCredentialAuthPrompt} instance that can be used for starting |
| * authentication. |
| * @return {@link Class3BiometricOrCredentialAuthPrompt} instance for starting |
| * authentication. |
| */ |
| @NonNull |
| public Class3BiometricOrCredentialAuthPrompt build() { |
| final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo |
| .Builder() |
| .setTitle(mTitle) |
| .setSubtitle(mSubtitle) |
| .setDescription(mDescription) |
| .setConfirmationRequired(mIsConfirmationRequired) |
| .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG |
| | BiometricManager.Authenticators.DEVICE_CREDENTIAL) |
| .build(); |
| |
| final BiometricPrompt biometricPrompt; |
| final BiometricPrompt.AuthenticationCallback wrappedCallback; |
| |
| if (mAuthPromptHost.getActivity() != null) { |
| wrappedCallback = new WrappedAuthPromptCallback(mClientCallback, |
| new ViewModelProvider(mAuthPromptHost.getActivity()) |
| .get(BiometricViewModel.class)); |
| biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(), |
| mClientExecutor, wrappedCallback); |
| } else if (mAuthPromptHost.getFragment() != null) { |
| wrappedCallback = new WrappedAuthPromptCallback(mClientCallback, |
| new ViewModelProvider(mAuthPromptHost.getFragment().getActivity()) |
| .get(BiometricViewModel.class)); |
| biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(), |
| mClientExecutor, wrappedCallback); |
| } else { |
| throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must " |
| + "provide AuthPromptHost containing Fragment or FragmentActivity for" |
| + " hosting the BiometricPrompt."); |
| } |
| return new Class3BiometricOrCredentialAuthPrompt(biometricPrompt, promptInfo, mCrypto, |
| mSubtitle, mDescription, mIsConfirmationRequired); |
| } |
| |
| /** |
| * Wraps AuthPromptCallback in BiometricPrompt.AuthenticationCallback for BiometricPrompt |
| * construction |
| */ |
| private static class WrappedAuthPromptCallback |
| extends BiometricPrompt.AuthenticationCallback { |
| @NonNull private final AuthPromptCallback mClientCallback; |
| @NonNull private final WeakReference<BiometricViewModel> mViewModelRef; |
| |
| WrappedAuthPromptCallback(@NonNull AuthPromptCallback callback, |
| @NonNull BiometricViewModel viewModel) { |
| mClientCallback = callback; |
| mViewModelRef = new WeakReference<>(viewModel); |
| } |
| |
| @Override |
| public void onAuthenticationError(int errorCode, |
| @NonNull CharSequence errString) { |
| if (mViewModelRef != null) { |
| mClientCallback.onAuthenticationError( |
| mViewModelRef.get().getClientActivity(), |
| errorCode, |
| errString |
| ); |
| } |
| } |
| |
| @Override |
| public void onAuthenticationSucceeded( |
| @NonNull BiometricPrompt.AuthenticationResult result) { |
| if (mViewModelRef != null) { |
| mClientCallback.onAuthenticationSucceeded( |
| mViewModelRef.get().getClientActivity(), |
| result |
| ); |
| } |
| } |
| |
| @Override |
| public void onAuthenticationFailed() { |
| if (mViewModelRef != null) { |
| mClientCallback.onAuthenticationFailed( |
| mViewModelRef.get().getClientActivity() |
| ); |
| } |
| } |
| } |
| } |
| } |