Skip to content

Instantly share code, notes, and snippets.

@haavamoa
Last active March 24, 2020 21:22
Show Gist options
  • Save haavamoa/c84a20abdcc4909f90e308fc83fae64c to your computer and use it in GitHub Desktop.
Save haavamoa/c84a20abdcc4909f90e308fc83fae64c to your computer and use it in GitHub Desktop.
Android BiometricsService using BiometricPrompt
using System;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Hardware.Biometrics;
using Android.OS;
using Android.Support.V4.App;
using Android.Support.V4.Hardware.Fingerprint;
using Java.Lang;
namespace DIPS.Xamarin.Essentials.Android.Authentication {
internal class BiometricsService : BiometricPrompt.AuthenticationCallback, IBiometricsService
{
internal static Activity MainActivity;
private TaskCompletionSource<BiometricsResponse> m_taskCompletionSource;
public Task<BiometricsResponse> Authenticate()
{
m_taskCompletionSource = new TaskCompletionSource<BiometricsResponse>();
if (!BiometricUtils.IsBiometricPromptEnabled())
{
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.NotAvailable, "BiometricPrompt not available"));
}
if (!BiometricUtils.IsSdkVersionSupported())
{
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.NotAvailable, "Android version is lower than Marshmallow, fingerprint authentication is only supported from Android 6.0"));
}
if (!BiometricUtils.IsHardwareSupported(MainActivity))
{
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.NotAvailable, "The device does not have fingerprint sensors"));
}
if (!BiometricUtils.IsFingerprintAvailable(MainActivity))
{
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.NotAvailable, "No fingerprints have been enrolled. Please go to settings and add fingerprints."));
}
var biometricPrompt = new BiometricPrompt.Builder(MainActivity).SetTitle("title").SetDescription("description")
.SetSubtitle("subtitle").SetNegativeButton("Cancel", MainActivity.MainExecutor, new DialogListener(OnCancel)).Build();
var cancellationSignal = new CancellationSignal();
biometricPrompt.Authenticate(
cancellationSignal,
MainActivity.MainExecutor,
this);
return m_taskCompletionSource.Task;
}
public override void OnAuthenticationError(BiometricErrorCode errorCode, ICharSequence errString)
{
if (m_taskCompletionSource.Task.IsCompleted)
return;
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.UnknownError, errString != null ? errString.ToString() : "Unknown error"));
base.OnAuthenticationError(errorCode, errString);
}
public override void OnAuthenticationFailed()
{
base.OnAuthenticationFailed();
}
public override void OnAuthenticationHelp(BiometricAcquiredStatus helpCode, ICharSequence helpString)
{
base.OnAuthenticationHelp(helpCode, helpString);
}
public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result)
{
if (m_taskCompletionSource.Task.IsCompleted) return;
m_taskCompletionSource.SetResult(new BiometricsResponse(BiometricsStatus.Succeeded, "Biometrics or pin-code succeeded"));
base.OnAuthenticationSucceeded(result);
}
private void OnCancel()
{
if (m_taskCompletionSource.Task.IsCompleted)
return;
m_taskCompletionSource.TrySetResult(new BiometricsResponse(BiometricsStatus.Canceled, "The user has canceled biometrics or pin-code"));
}
}
public class DialogListener : Java.Lang.Object, IDialogInterfaceOnClickListener
{
private Action m_onCancel;
public DialogListener(Action onCancel)
{
m_onCancel = onCancel;
}
public void OnClick(IDialogInterface dialog, int which)
{
m_onCancel.Invoke();
}
}
public class BiometricUtils
{
public static bool CheckAll(Context context) =>
IsBiometricPromptEnabled() && IsSdkVersionSupported() && IsSdkVersionSupported() && IsHardwareSupported(context) &&
IsFingerprintAvailable(context) && IsPermissionGranted(context);
public static bool IsBiometricPromptEnabled()
{
return (Build.VERSION.SdkInt >= Build.VERSION_CODES.P);
}
/// <summary>
/// Condition I: Check if the android version in device is greater than
/// Marshmallow, since fingerprint authentication is only supported
/// from Android 6.0.
/// Note: If your project's minSdkversion is 23 or higher,
/// then you won't need to perform this check.
/// </summary>
/// <returns></returns>
public static bool IsSdkVersionSupported() => (Build.VERSION.SdkInt >= BuildVersionCodes.M);
/// <summary>
/// Condition II: Check if the device has fingerprint sensors.
/// Note: If you marked android.hardware.fingerprint as something that
/// your app requires (android:required= "true"), then you don't need
/// to perform this check.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool IsHardwareSupported(Context context) => FingerprintManagerCompat.From(context).IsHardwareDetected;
/// <summary>
/// Condition III: Fingerprint authentication can be matched with a
/// registered fingerprint of the user. So we need to perform this check</summary>
/// in order to enable fingerprint authentication<param name="context"></param>
/// <returns></returns>
public static bool IsFingerprintAvailable(Context context) => FingerprintManagerCompat.From(context).HasEnrolledFingerprints;
/// <summary>
/// Condition IV: Check if the permission has been added to
/// the app. This permission will be granted as soon as the user</summary>
/// installs the app on their device.<param name="context"></param>
/// <returns></returns>
public static bool IsPermissionGranted(Context context)
{
return ActivityCompat.CheckSelfPermission(context, Manifest.Permission.UseFingerprint) == Permission.Granted &&
ActivityCompat.CheckSelfPermission(context, Manifest.Permission.UseBiometric) == Permission.Granted;
}
}
internal interface IBiometricsService {
Task<BiometricsResponse> Authenticate();
}
internal class BiometricsResponse
{
public BiometricsStatus Status { get; }
public string Message { get; }
public BiometricsResponse(BiometricsStatus status, string message)
{
Status = status;
Message = message;
}
}
public enum BiometricsStatus
{
Unknown,
Succeeded,
FallbackRequested,
Failed,
Canceled,
TooManyAttempts,
UnknownError,
NotAvailable,
Denied,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment