public class

BiometricViewModel

extends ViewModel

 java.lang.Object

androidx.lifecycle.ViewModel

↳androidx.biometric.BiometricViewModel

Gradle dependencies

compile group: 'androidx.biometric', name: 'biometric', version: '1.2.0-alpha04'

  • groupId: androidx.biometric
  • artifactId: biometric
  • version: 1.2.0-alpha04

Artifact androidx.biometric:biometric:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Overview

A container for data associated with an ongoing authentication session, including intermediate values needed to display the prompt UI.

This model and all of its data is persisted over the lifetime of the client activity that hosts the BiometricPrompt.

Summary

Constructors
publicBiometricViewModel()

Methods
public FragmentActivitygetClientActivity()

Returns reference to latest activity hosting BiometricPrompt or null if activity has already been destroyed

from ViewModeladdCloseable, onCleared
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public BiometricViewModel()

Methods

public FragmentActivity getClientActivity()

Returns reference to latest activity hosting BiometricPrompt or null if activity has already been destroyed

Returns:

Reference to latest activity hosting BiometricPrompt

Source

/*
 * 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;

import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;

/**
 * A container for data associated with an ongoing authentication session, including intermediate
 * values needed to display the prompt UI.
 *
 * <p>This model and all of its data is persisted over the lifetime of the client activity that
 * hosts the {@link BiometricPrompt}.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class BiometricViewModel extends ViewModel {
    /**
     * The default executor provided when {@link #getClientExecutor()} is called before
     * {@link #setClientExecutor(Executor)}.
     */
    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);
        }
    }

    /**
     * The authentication callback listener passed to {@link AuthenticationCallbackProvider} when
     * {@link #getAuthenticationCallbackProvider()} is called.
     */
    private static final class CallbackListener extends AuthenticationCallbackProvider.Listener {
        @NonNull private final WeakReference<BiometricViewModel> mViewModelRef;

        /**
         * Creates a callback listener with a weak reference to the given view model.
         *
         * @param viewModel The view model instance to hold a weak reference to.
         */
        @SuppressWarnings("WeakerAccess") /* synthetic access */
        CallbackListener(@Nullable BiometricViewModel viewModel) {
            mViewModelRef = new WeakReference<>(viewModel);
        }

        @Override
        void onSuccess(@NonNull BiometricPrompt.AuthenticationResult result) {
            if (mViewModelRef.get() != null && mViewModelRef.get().isAwaitingResult()) {
                // Try to infer the authentication type if unknown.
                if (result.getAuthenticationType()
                        == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN) {
                    result = new BiometricPrompt.AuthenticationResult(
                            result.getCryptoObject(),
                            mViewModelRef.get().getInferredAuthenticationResultType());
                }

                mViewModelRef.get().setAuthenticationResult(result);
            }
        }

        @Override
        void onError(int errorCode, @Nullable CharSequence errorMessage) {
            if (mViewModelRef.get() != null
                    && !mViewModelRef.get().isConfirmingDeviceCredential()
                    && mViewModelRef.get().isAwaitingResult()) {
                mViewModelRef.get().setAuthenticationError(
                        new BiometricErrorData(errorCode, errorMessage));
            }
        }

        @Override
        void onHelp(@Nullable CharSequence helpMessage) {
            if (mViewModelRef.get() != null) {
                mViewModelRef.get().setAuthenticationHelpMessage(helpMessage);
            }
        }

        @Override
        void onFailure() {
            if (mViewModelRef.get() != null && mViewModelRef.get().isAwaitingResult()) {
                mViewModelRef.get().setAuthenticationFailurePending(true);
            }
        }
    }

    /**
     * The dialog listener that is returned by {@link #getNegativeButtonListener()}.
     */
    private static class NegativeButtonListener implements DialogInterface.OnClickListener {
        @NonNull private final WeakReference<BiometricViewModel> mViewModelRef;

        /**
         * Creates a negative button listener with a weak reference to the given view model.
         *
         * @param viewModel The view model instance to hold a weak reference to.
         */
        @SuppressWarnings("WeakerAccess") /* synthetic access */
        NegativeButtonListener(@Nullable BiometricViewModel viewModel) {
            mViewModelRef = new WeakReference<>(viewModel);
        }

        @Override
        public void onClick(DialogInterface dialogInterface, int which) {
            if (mViewModelRef.get() != null) {
                mViewModelRef.get().setNegativeButtonPressPending(true);
            }
        }
    }

    /**
     * The executor that will run authentication callback methods.
     *
     * <p>If unset, callbacks are invoked on the main thread with {@link Looper#getMainLooper()}.
     */
    @Nullable private Executor mClientExecutor;

    /**
     * The callback object that will receive authentication events.
     */
    @Nullable private BiometricPrompt.AuthenticationCallback mClientCallback;

    /**
     * Reference to latest {@link androidx.fragment.app.FragmentActivity} hosting BiometricPrompt
     */
    @Nullable private WeakReference<FragmentActivity> mClientActivity;

    /**
     * Info about the appearance and behavior of the prompt provided by the client application.
     */
    @Nullable private BiometricPrompt.PromptInfo mPromptInfo;

    /**
     * The crypto object associated with the current authentication session.
     */
    @Nullable private BiometricPrompt.CryptoObject mCryptoObject;

    /**
     * A provider for cross-platform compatible authentication callbacks.
     */
    @Nullable private AuthenticationCallbackProvider mAuthenticationCallbackProvider;

    /**
     * A provider for cross-platform compatible cancellation signal objects.
     */
    @Nullable private CancellationSignalProvider mCancellationSignalProvider;

    /**
     * A dialog listener for the negative button shown on the prompt.
     */
    @Nullable private DialogInterface.OnClickListener mNegativeButtonListener;

    /**
     * A label for the negative button shown on the prompt.
     *
     * <p>If set, this value overrides the one returned by
     * {@link BiometricPrompt.PromptInfo#getNegativeButtonText()}.
     */
    @Nullable private CharSequence mNegativeButtonTextOverride;

    /**
     * An integer indicating where the dialog was last canceled from.
     */
    @BiometricFragment.CanceledFrom
    private int mCanceledFrom = BiometricFragment.CANCELED_FROM_INTERNAL;

    /**
     * Whether the prompt is currently showing.
     */
    private boolean mIsPromptShowing;

    /**
     * Whether the client callback is awaiting an authentication result.
     */
    private boolean mIsAwaitingResult;

    /**
     * Whether the user is currently authenticating with their PIN, pattern, or password.
     */
    private boolean mIsConfirmingDeviceCredential;

    /**
     * Whether the prompt should delay showing the authentication UI.
     */
    private boolean mIsDelayingPrompt;

    /**
     * Whether the prompt should ignore cancel requests not initiated by the client.
     */
    private boolean mIsIgnoringCancel;

    /**
     * Whether {@link android.app.KeyguardManager} is being used directly for authentication with
     * both biometric and credential authenticator types allowed.
     */
    private boolean mIsUsingKeyguardManagerForBiometricAndCredential;

    /**
     * Information associated with a successful authentication attempt.
     */
    @Nullable private MutableLiveData<BiometricPrompt.AuthenticationResult> mAuthenticationResult;

    /**
     * Information associated with an unrecoverable authentication error.
     */
    @Nullable private MutableLiveData<BiometricErrorData> mAuthenticationError;

    /**
     * A human-readable message describing a recoverable authentication error or event.
     */
    @Nullable private MutableLiveData<CharSequence> mAuthenticationHelpMessage;

    /**
     * Whether an unrecognized biometric has been presented.
     */
    @Nullable private MutableLiveData<Boolean> mIsAuthenticationFailurePending;

    /**
     * Whether the user has pressed the negative button on the prompt.
     */
    @Nullable private MutableLiveData<Boolean> mIsNegativeButtonPressPending;

    /**
     * Whether the fingerprint dialog should always be dismissed instantly.
     */
    private boolean mIsFingerprintDialogDismissedInstantly = true;

    /**
     * Whether the user has manually canceled out of the fingerprint dialog.
     */
    @Nullable private MutableLiveData<Boolean> mIsFingerprintDialogCancelPending;

    /**
     * The previous state of the fingerprint dialog UI.
     */
    @FingerprintDialogFragment.State
    private int mFingerprintDialogPreviousState = FingerprintDialogFragment.STATE_NONE;

    /**
     * The current state of the fingerprint dialog UI.
     */
    @Nullable private MutableLiveData<Integer> mFingerprintDialogState;

    /**
     * A human-readable message to be displayed below the icon on the fingerprint dialog.
     */
    @Nullable private MutableLiveData<CharSequence> mFingerprintDialogHelpMessage;

    @NonNull
    Executor getClientExecutor() {
        return mClientExecutor != null ? mClientExecutor : new DefaultExecutor();
    }

    void setClientExecutor(@NonNull Executor clientExecutor) {
        mClientExecutor = clientExecutor;
    }

    @NonNull
    BiometricPrompt.AuthenticationCallback getClientCallback() {
        if (mClientCallback == null) {
            mClientCallback = new BiometricPrompt.AuthenticationCallback() {};
        }
        return mClientCallback;
    }

    void setClientCallback(@NonNull BiometricPrompt.AuthenticationCallback clientCallback) {
        mClientCallback = clientCallback;
    }

    /**
     * Clears the client callback reference held by this view model.
     */
    void resetClientCallback() {
        mClientCallback = null;
    }

    /**
     * Returns reference to latest activity hosting BiometricPrompt or null if activity has
     * already been destroyed
     * @return Reference to latest activity hosting BiometricPrompt
     */
    @Nullable
    public FragmentActivity getClientActivity() {
        return mClientActivity != null ? mClientActivity.get() : null;
    }

    /**
     * Updates reference to latest activity hosting BiometricPrompt
     * @param clientActivity Reference to latest activity hosting BiometricPrompt
     */
    void setClientActivity(@NonNull FragmentActivity clientActivity) {
        mClientActivity = new WeakReference<>(clientActivity);
    }

    void setPromptInfo(@Nullable BiometricPrompt.PromptInfo promptInfo) {
        mPromptInfo = promptInfo;
    }

    /**
     * Gets the title to be shown on the biometric prompt.
     *
     * <p>This method relies on the {@link BiometricPrompt.PromptInfo} set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}.
     *
     * @return The title for the prompt, or {@code null} if not set.
     */
    @Nullable
    CharSequence getTitle() {
        return mPromptInfo != null ? mPromptInfo.getTitle() : null;
    }

    /**
     * Gets the subtitle to be shown on the biometric prompt.
     *
     * <p>This method relies on the {@link BiometricPrompt.PromptInfo} set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}.
     *
     * @return The subtitle for the prompt, or {@code null} if not set.
     */
    @Nullable
    CharSequence getSubtitle() {
        return mPromptInfo != null ? mPromptInfo.getSubtitle() : null;
    }

    /**
     * Gets the description to be shown on the biometric prompt.
     *
     * <p>This method relies on the {@link BiometricPrompt.PromptInfo} set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}.
     *
     * @return The description for the prompt, or {@code null} if not set.
     */
    @Nullable
    CharSequence getDescription() {
        return mPromptInfo != null ? mPromptInfo.getDescription() : null;
    }

    /**
     * Gets the text that should be shown for the negative button on the biometric prompt.
     *
     * <p>If non-null, the value set by {@link #setNegativeButtonTextOverride(CharSequence)} is
     * used. Otherwise, falls back to the value returned by
     * {@link BiometricPrompt.PromptInfo#getNegativeButtonText()}, or {@code null} if a non-null
     * {@link BiometricPrompt.PromptInfo} has not been set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}.
     *
     * @return The negative button text for the prompt, or {@code null} if not set.
     */
    @Nullable
    CharSequence getNegativeButtonText() {
        if (mNegativeButtonTextOverride != null) {
            return mNegativeButtonTextOverride;
        } else if (mPromptInfo != null) {
            return mPromptInfo.getNegativeButtonText();
        } else {
            return null;
        }
    }

    /**
     * Checks if the confirmation required option is enabled for the biometric prompt.
     *
     * <p>This method relies on the {@link BiometricPrompt.PromptInfo} set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}.
     *
     * @return Whether the confirmation required option is enabled.
     */
    boolean isConfirmationRequired() {
        return mPromptInfo == null || mPromptInfo.isConfirmationRequired();
    }

    /**
     * Gets the type(s) of authenticators that may be invoked by the biometric prompt.
     *
     * <p>If a non-null {@link BiometricPrompt.PromptInfo} has been set by
     * {@link #setPromptInfo(BiometricPrompt.PromptInfo)}, this is the single consolidated set of
     * authenticators allowed by the prompt, taking into account the values of
     * {@link BiometricPrompt.PromptInfo#getAllowedAuthenticators()},
     * {@link BiometricPrompt.PromptInfo#isDeviceCredentialAllowed()}, and
     * {@link #getCryptoObject()}.
     *
     * @return A bit field representing all valid authenticator types that may be invoked by
     * the prompt, or 0 if not set.
     */
    @SuppressWarnings("deprecation")
    @BiometricManager.AuthenticatorTypes
    int getAllowedAuthenticators() {
        return mPromptInfo != null
                ? AuthenticatorUtils.getConsolidatedAuthenticators(mPromptInfo, mCryptoObject)
                : 0;
    }

    @Nullable
    BiometricPrompt.CryptoObject getCryptoObject() {
        return mCryptoObject;
    }

    void setCryptoObject(@Nullable BiometricPrompt.CryptoObject cryptoObject) {
        mCryptoObject = cryptoObject;
    }

    @NonNull
    AuthenticationCallbackProvider getAuthenticationCallbackProvider() {
        if (mAuthenticationCallbackProvider == null) {
            mAuthenticationCallbackProvider =
                    new AuthenticationCallbackProvider(new CallbackListener(this));
        }
        return mAuthenticationCallbackProvider;
    }

    @NonNull
    CancellationSignalProvider getCancellationSignalProvider() {
        if (mCancellationSignalProvider == null) {
            mCancellationSignalProvider = new CancellationSignalProvider();
        }
        return mCancellationSignalProvider;
    }

    @NonNull
    DialogInterface.OnClickListener getNegativeButtonListener() {
        if (mNegativeButtonListener == null) {
            mNegativeButtonListener = new NegativeButtonListener(this);
        }
        return mNegativeButtonListener;
    }

    void setNegativeButtonTextOverride(@Nullable CharSequence negativeButtonTextOverride) {
        mNegativeButtonTextOverride = negativeButtonTextOverride;
    }

    int getCanceledFrom() {
        return mCanceledFrom;
    }

    void setCanceledFrom(int canceledFrom) {
        mCanceledFrom = canceledFrom;
    }

    boolean isPromptShowing() {
        return mIsPromptShowing;
    }

    void setPromptShowing(boolean promptShowing) {
        mIsPromptShowing = promptShowing;
    }

    boolean isAwaitingResult() {
        return mIsAwaitingResult;
    }

    void setAwaitingResult(boolean awaitingResult) {
        mIsAwaitingResult = awaitingResult;
    }

    boolean isConfirmingDeviceCredential() {
        return mIsConfirmingDeviceCredential;
    }

    void setConfirmingDeviceCredential(boolean confirmingDeviceCredential) {
        mIsConfirmingDeviceCredential = confirmingDeviceCredential;
    }

    boolean isDelayingPrompt() {
        return mIsDelayingPrompt;
    }

    void setDelayingPrompt(boolean delayingPrompt) {
        mIsDelayingPrompt = delayingPrompt;
    }

    boolean isIgnoringCancel() {
        return mIsIgnoringCancel;
    }

    void setIgnoringCancel(boolean ignoringCancel) {
        mIsIgnoringCancel = ignoringCancel;
    }

    boolean isUsingKeyguardManagerForBiometricAndCredential() {
        return mIsUsingKeyguardManagerForBiometricAndCredential;
    }

    void setUsingKeyguardManagerForBiometricAndCredential(
            boolean usingKeyguardManagerForBiometricAndCredential) {
        mIsUsingKeyguardManagerForBiometricAndCredential =
                usingKeyguardManagerForBiometricAndCredential;
    }

    @NonNull
    LiveData<BiometricPrompt.AuthenticationResult> getAuthenticationResult() {
        if (mAuthenticationResult == null) {
            mAuthenticationResult = new MutableLiveData<>();
        }
        return mAuthenticationResult;
    }

    void setAuthenticationResult(
            @Nullable BiometricPrompt.AuthenticationResult authenticationResult) {
        if (mAuthenticationResult == null) {
            mAuthenticationResult = new MutableLiveData<>();
        }
        updateValue(mAuthenticationResult, authenticationResult);
    }

    @NonNull
    MutableLiveData<BiometricErrorData> getAuthenticationError() {
        if (mAuthenticationError == null) {
            mAuthenticationError = new MutableLiveData<>();
        }
        return mAuthenticationError;
    }

    void setAuthenticationError(@Nullable BiometricErrorData authenticationError) {
        if (mAuthenticationError == null) {
            mAuthenticationError = new MutableLiveData<>();
        }
        updateValue(mAuthenticationError, authenticationError);
    }

    @NonNull
    LiveData<CharSequence> getAuthenticationHelpMessage() {
        if (mAuthenticationHelpMessage == null) {
            mAuthenticationHelpMessage = new MutableLiveData<>();
        }
        return mAuthenticationHelpMessage;
    }

    void setAuthenticationHelpMessage(
            @Nullable CharSequence authenticationHelpMessage) {
        if (mAuthenticationHelpMessage == null) {
            mAuthenticationHelpMessage = new MutableLiveData<>();
        }
        updateValue(mAuthenticationHelpMessage, authenticationHelpMessage);
    }

    @NonNull
    LiveData<Boolean> isAuthenticationFailurePending() {
        if (mIsAuthenticationFailurePending == null) {
            mIsAuthenticationFailurePending = new MutableLiveData<>();
        }
        return mIsAuthenticationFailurePending;
    }

    void setAuthenticationFailurePending(boolean authenticationFailurePending) {
        if (mIsAuthenticationFailurePending == null) {
            mIsAuthenticationFailurePending = new MutableLiveData<>();
        }
        updateValue(mIsAuthenticationFailurePending, authenticationFailurePending);
    }

    @NonNull
    LiveData<Boolean> isNegativeButtonPressPending() {
        if (mIsNegativeButtonPressPending == null) {
            mIsNegativeButtonPressPending = new MutableLiveData<>();
        }
        return mIsNegativeButtonPressPending;
    }

    void setNegativeButtonPressPending(boolean negativeButtonPressPending) {
        if (mIsNegativeButtonPressPending == null) {
            mIsNegativeButtonPressPending = new MutableLiveData<>();
        }
        updateValue(mIsNegativeButtonPressPending, negativeButtonPressPending);
    }

    boolean isFingerprintDialogDismissedInstantly() {
        return mIsFingerprintDialogDismissedInstantly;
    }

    void setFingerprintDialogDismissedInstantly(
            boolean fingerprintDialogDismissedInstantly) {
        mIsFingerprintDialogDismissedInstantly = fingerprintDialogDismissedInstantly;
    }

    @NonNull
    LiveData<Boolean> isFingerprintDialogCancelPending() {
        if (mIsFingerprintDialogCancelPending == null) {
            mIsFingerprintDialogCancelPending = new MutableLiveData<>();
        }
        return mIsFingerprintDialogCancelPending;
    }

    void setFingerprintDialogCancelPending(boolean fingerprintDialogCancelPending) {
        if (mIsFingerprintDialogCancelPending == null) {
            mIsFingerprintDialogCancelPending = new MutableLiveData<>();
        }
        updateValue(mIsFingerprintDialogCancelPending, fingerprintDialogCancelPending);
    }

    @FingerprintDialogFragment.State
    int getFingerprintDialogPreviousState() {
        return mFingerprintDialogPreviousState;
    }

    void setFingerprintDialogPreviousState(
            @FingerprintDialogFragment.State int fingerprintDialogPreviousState) {
        mFingerprintDialogPreviousState = fingerprintDialogPreviousState;
    }

    @NonNull
    LiveData<Integer> getFingerprintDialogState() {
        if (mFingerprintDialogState == null) {
            mFingerprintDialogState = new MutableLiveData<>();
        }
        return mFingerprintDialogState;
    }

    void setFingerprintDialogState(
            @FingerprintDialogFragment.State int fingerprintDialogState) {
        if (mFingerprintDialogState == null) {
            mFingerprintDialogState = new MutableLiveData<>();
        }
        updateValue(mFingerprintDialogState, fingerprintDialogState);
    }

    @NonNull
    LiveData<CharSequence> getFingerprintDialogHelpMessage() {
        if (mFingerprintDialogHelpMessage == null) {
            mFingerprintDialogHelpMessage = new MutableLiveData<>();
        }
        return mFingerprintDialogHelpMessage;
    }

    void setFingerprintDialogHelpMessage(
            @NonNull CharSequence fingerprintDialogHelpMessage) {
        if (mFingerprintDialogHelpMessage == null) {
            mFingerprintDialogHelpMessage = new MutableLiveData<>();
        }
        updateValue(mFingerprintDialogHelpMessage, fingerprintDialogHelpMessage);
    }

    /**
     * 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;
    }

    /**
     * Ensures the value of a given mutable live data object is updated on the main thread.
     *
     * @param liveData The mutable live data object whose value should be updated.
     * @param value    The new value to be set for the mutable live data object.
     */
    private static <T> void updateValue(MutableLiveData<T> liveData, T value) {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            liveData.setValue(value);
        } else {
            liveData.postValue(value);
        }
    }
}