AuthPromptUtils.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.auth;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.biometric.BiometricPrompt;
import androidx.biometric.BiometricViewModel;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
/**
* Utilities used by various auth prompt classes.
*/
class AuthPromptUtils {
// Prevent instantiation.
private AuthPromptUtils() {}
/**
* Shows an authentication prompt to the user.
*
* @param host A wrapper for the component that will host the prompt.
* @param promptInfo A set of options describing how the prompt should appear and behave.
* @param crypto A cryptographic object to be associated with this authentication.
* @param executor A custom executor that will be used to run callback methods. If
* {@code null}, callback methods will be run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return A handle to the shown prompt.
*/
@NonNull
static AuthPrompt startAuthentication(
@NonNull AuthPromptHost host,
@NonNull BiometricPrompt.PromptInfo promptInfo,
@Nullable BiometricPrompt.CryptoObject crypto,
@Nullable Executor executor,
@NonNull AuthPromptCallback callback) {
final BiometricPrompt biometricPrompt =
AuthPromptUtils.createBiometricPrompt(host, executor, callback);
if (crypto == null) {
biometricPrompt.authenticate(promptInfo);
} else {
biometricPrompt.authenticate(promptInfo, crypto);
}
return new AuthPromptWrapper(biometricPrompt);
}
/**
* Creates a {@link BiometricPrompt} with the given parameters.
*
* @param host A wrapper for the component that will host the prompt.
* @param executor A custom executor that will be used to run callback methods. If {@code null},
* callback methods will be run on the main thread.
* @param callback The object that will receive and process authentication events.
* @return An instance of {@link BiometricPrompt}.
*
* @throws IllegalArgumentException If the given host wrapper does not contain an activity or a
* fragment that is associated with an activity.
*/
@NonNull
private static BiometricPrompt createBiometricPrompt(
@NonNull AuthPromptHost host,
@Nullable Executor executor,
@NonNull AuthPromptCallback callback) {
final Executor executorOrDefault = executor != null ? executor : new DefaultExecutor();
final BiometricPrompt prompt;
if (host.getActivity() != null) {
final ViewModelProvider provider = new ViewModelProvider(host.getActivity());
final AuthenticationCallbackWrapper wrappedCallback = wrapCallback(callback, provider);
prompt = new BiometricPrompt(host.getActivity(), executorOrDefault, wrappedCallback);
} else if (host.getFragment() != null && host.getFragment().getActivity() != null) {
final FragmentActivity activity = host.getFragment().getActivity();
final ViewModelProvider provider = new ViewModelProvider(activity);
final AuthenticationCallbackWrapper wrappedCallback = wrapCallback(callback, provider);
prompt = new BiometricPrompt(host.getFragment(), executorOrDefault, wrappedCallback);
} else {
throw new IllegalArgumentException("AuthPromptHost must contain a FragmentActivity or"
+ " an attached Fragment.");
}
return prompt;
}
/**
* Wraps the given callback in a new {@link AuthenticationCallbackWrapper} instance, for
* compatibility with {@link BiometricPrompt}.
*
* @param callback A callback object that is compatible with {@link BiometricPrompt}.
* @param provider A provider that can be used to get a {@link BiometricViewModel} instance.
* @return An instance of {@link AuthenticationCallbackWrapper} that wraps the given callback.
*/
private static AuthenticationCallbackWrapper wrapCallback(
@NonNull AuthPromptCallback callback, @NonNull ViewModelProvider provider) {
return new AuthenticationCallbackWrapper(callback, provider.get(BiometricViewModel.class));
}
/**
* A wrapper class that provides an {@link AuthPrompt} interface for a {@link BiometricPrompt}.
*/
private static class AuthPromptWrapper implements AuthPrompt {
@NonNull private final WeakReference<BiometricPrompt> mBiometricPromptRef;
/**
* Constructs an {@link AuthPromptWrapper} interface for the given prompt.
*
* @param biometricPrompt An instance of {@link BiometricPrompt}.
*/
AuthPromptWrapper(@NonNull BiometricPrompt biometricPrompt) {
mBiometricPromptRef = new WeakReference<>(biometricPrompt);
}
@Override
public void cancelAuthentication() {
if (mBiometricPromptRef.get() != null) {
mBiometricPromptRef.get().cancelAuthentication();
}
}
}
/**
* The default executor class used to run authentication callback methods on the main thread.
*/
private static class DefaultExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@SuppressWarnings("WeakerAccess") /* synthetic access */
DefaultExecutor() {}
@Override
public void execute(@NonNull Runnable runnable) {
mHandler.post(runnable);
}
}
/**
* A wrapper class that provides a {@link BiometricPrompt.AuthenticationCallback} interface for
* an {@link AuthPromptCallback}.
*/
private static class AuthenticationCallbackWrapper
extends BiometricPrompt.AuthenticationCallback {
@NonNull private final AuthPromptCallback mClientCallback;
@NonNull private final WeakReference<BiometricViewModel> mViewModelRef;
/**
* Creates an {@link AuthenticationCallbackWrapper} with the given parameters.
*
* @param callback A callback object that is compatible with {@link BiometricPrompt}.
* @param viewModel A {@link BiometricViewModel} that maintains a reference to the host
* activity across configuration changes.
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
AuthenticationCallbackWrapper(
@NonNull AuthPromptCallback callback,
@NonNull BiometricViewModel viewModel) {
mClientCallback = callback;
mViewModelRef = new WeakReference<>(viewModel);
}
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
mClientCallback.onAuthenticationError(getActivity(mViewModelRef), errorCode, errString);
}
@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
mClientCallback.onAuthenticationSucceeded(getActivity(mViewModelRef), result);
}
@Override
public void onAuthenticationFailed() {
mClientCallback.onAuthenticationFailed(getActivity(mViewModelRef));
}
@Nullable
private static FragmentActivity getActivity(
@NonNull WeakReference<BiometricViewModel> viewModelRef) {
return viewModelRef.get() != null ? viewModelRef.get().getClientActivity() : null;
}
}
}