BiometricViewModel.java

/*
 * 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.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

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 executor that will run authentication callback methods.
     */
    @Nullable private Executor mClientExecutor;

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

    /**
     * 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 {@code null}, this value is instead read from the current
     * {@link androidx.biometric.BiometricPrompt.PromptInfo}.
     */
    @Nullable private CharSequence mNegativeButtonText;

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

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

    /**
     * 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() {
        if (mClientExecutor == null) {
            final Handler handler = new Handler(Looper.getMainLooper());
            mClientExecutor = new Executor() {
                @Override
                public void execute(@NonNull Runnable command) {
                    handler.post(command);
                }
            };
        }
        return mClientExecutor;
    }

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

    @Nullable
    BiometricPrompt.PromptInfo getPromptInfo() {
        return mPromptInfo;
    }

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

    @Nullable
    CharSequence getTitle() {
        return mPromptInfo != null ? mPromptInfo.getTitle() : null;
    }

    @Nullable
    CharSequence getSubtitle() {
        return mPromptInfo != null ? mPromptInfo.getSubtitle() : null;
    }

    @Nullable
    CharSequence getDescription() {
        return mPromptInfo != null ? mPromptInfo.getDescription() : null;
    }

    boolean isDeviceCredentialAllowed() {
        return mPromptInfo != null && mPromptInfo.isDeviceCredentialAllowed();
    }

    boolean isConfirmationRequired() {
        return mPromptInfo == null || mPromptInfo.isConfirmationRequired();
    }

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

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

    @NonNull
    AuthenticationCallbackProvider getAuthenticationCallbackProvider() {
        if (mAuthenticationCallbackProvider == null) {
            mAuthenticationCallbackProvider = new AuthenticationCallbackProvider(
                    new AuthenticationCallbackProvider.Listener() {
                        @Override
                        void onSuccess(@NonNull BiometricPrompt.AuthenticationResult result) {
                            if (isAwaitingResult()) {
                                setAuthenticationResult(result);
                            }
                        }

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

                        @Override
                        void onHelp(@Nullable CharSequence helpMessage) {
                            setAuthenticationHelpMessage(helpMessage);
                        }

                        @Override
                        void onFailure() {
                            if (isAwaitingResult()) {
                                setAuthenticationFailurePending(true);
                            }
                        }
                    }
            );
        }
        return mAuthenticationCallbackProvider;
    }

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

    @NonNull
    DialogInterface.OnClickListener getNegativeButtonListener() {
        if (mNegativeButtonListener == null) {
            mNegativeButtonListener = new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    setNegativeButtonPressPending(true);
                }
            };
        }
        return mNegativeButtonListener;
    }

    @Nullable
    CharSequence getNegativeButtonText() {
        if (mNegativeButtonText != null) {
            return mNegativeButtonText;
        } else if (mPromptInfo != null) {
            return mPromptInfo.getNegativeButtonText();
        } else {
            return null;
        }
    }

    void setNegativeButtonText(@Nullable CharSequence negativeButtonText) {
        mNegativeButtonText = negativeButtonText;
    }

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

    @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<>();
        }
        mAuthenticationResult.setValue(authenticationResult);
    }

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

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

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

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

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

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

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

    void setNegativeButtonPressPending(boolean negativeButtonPressPending) {
        if (mIsNegativeButtonPressPending == null) {
            mIsNegativeButtonPressPending = new MutableLiveData<>();
        }
        mIsNegativeButtonPressPending.setValue(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<>();
        }
        mIsFingerprintDialogCancelPending.setValue(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<>();
        }
        mFingerprintDialogState.setValue(fingerprintDialogState);
    }

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

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