49

I'm asked to show certain UI elements depending on the presence of biometric hardware. For Android 23-27 I use FingerprintManager#isHardwareDetected() and FingerprintManager#hasEnrolledFingerprints(). Both of which are deprecated in Android 28.

I understand that I can get this information by using BiometricPrompt#authenticate(...) and receiving either BiometricPrompt#BIOMETRIC_ERROR_HW_NOT_PRESENT or BiometricPrompt#BIOMETRIC_ERROR_NO_BIOMETRICS in the BiometricPrompt.AuthenticationCallback#onAuthenticationError(int errorCode, ...) method. But this would lead to the BiometricPrompt being shown on supporting devices, which is undesirable. Using the CancellationSignal doesn't seem to be a solution either, since I wouldn't know when to cancel the prompt.

Is there any way to detect biometric hardware presence and user enrolment?

sirius
  • 1,019
  • 1
  • 10
  • 16
  • 1
    Corresponding issue in the Android bug tracker: https://issuetracker.google.com/issues/109826221 – sirius Jun 26 '18 at 14:29
  • any luck with this yet? – Rahul Jul 10 '18 at 09:26
  • 1
    @Rahul No. There was an update in the Android bug tracker: "You can check for PackageManager.FEATURE_FINGERPRINT, which is currently the only supported biometric for BiometricPrompt." I didn't try it yet. – sirius Jul 12 '18 at 09:03
  • https://medium.com/@ghodasarabhaumik/android-fingerprint-enrolment-detection-detect-fingerprint-added-removed-68f8189766f9 – Bhaumik Ghodasara Oct 13 '22 at 11:55

8 Answers8

27

Google finally solved this problem with Android Q

The android.hardware.biometrics.BiometricManager#canAuthenticate() method can be used to determine if biometrics can be used.

The method can be used to determine if biometric hardware is present and if the user is enrolled or not.

Returns BIOMETRIC_ERROR_NONE_ENROLLED if the user does not have any enrolled, or BIOMETRIC_ERROR_HW_UNAVAILABLE if none are currently supported/enabled. Returns BIOMETRIC_SUCCESS if a biometric can currently be used (enrolled and available).

Hopefully this is added to the androidx.biometric:biometric library, so it can be used on all devices.

Until then the solution by @algrid works to determine biometrics enrollment.

And the following can be used to determine, if a fingerprint reader is present.

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
            context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
sirius
  • 1,019
  • 1
  • 10
  • 16
  • 3
    This only works for Android 10, for 6 through 9 it will return HW_UNAVAILABLE when face, iris, etc.. is enrolled (with the exception of fingerprint) – James Sep 10 '19 at 18:40
  • 1
    @olearyj234 do you know any idea to detect face, iris ...etc is enrolled when finger print is not? – ppnfk Aug 05 '20 at 06:18
18

AndroidX biometric library started providing this kind of information from version 1.0.0-beta01 (androidx.biometric:biometric:1.0.0-beta01)

BiometricManager.from(context).canAuthenticate()

Which returns one of

  • BIOMETRIC_SUCCESS
  • BIOMETRIC_ERROR_HW_UNAVAILABLE
  • BIOMETRIC_ERROR_NONE_ENROLLED
  • BIOMETRIC_ERROR_NO_HARDWARE

See changelog: https://developer.android.com/jetpack/androidx/releases/biometric#1.0.0-beta01

mlykotom
  • 4,850
  • 1
  • 22
  • 27
  • This only works for Android 10, for 6 through 9 it will return HW_UNAVAILABLE for face, iris, etc.. with the exception of fingerprint – James Sep 10 '19 at 18:30
  • There is actually bug in the library (which should be in next release?) See issue tracker https://issuetracker.google.com/issues/140427586 – mlykotom Sep 10 '19 at 19:16
  • that issue is for api < 23; not sure what issue @olearyj234 is referring to but it's possibly the Samsung-specific https://issuetracker.google.com/issues/140398825 (with a different error code) – Gabor Sep 12 '19 at 04:54
  • possibly it's different issue – mlykotom Sep 12 '19 at 06:45
9

Sadly Google wouldn't solve this problem having changed the status of related issue to "Won't Fix (Intended behavior)". I prefer to use the old deprecated API for now.

But for those who want to use the newer API there's a hacky/ugly way to get a hasEnrolledFingerprints() analog (the code is for API23+):

public boolean isBiometryAvailable() {
    KeyStore keyStore;
    try {
        keyStore = KeyStore.getInstance("AndroidKeyStore");
    } catch (Exception e) {
        return false;
    }

    KeyGenerator keyGenerator;
    try {
        keyGenerator = KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    } catch (NoSuchAlgorithmException |
            NoSuchProviderException e) {
        return false;
    }

    if (keyGenerator == null || keyStore == null) {
        return false;
    }

    try {
        keyStore.load(null);
        keyGenerator.init(new
                KeyGenParameterSpec.Builder("dummy_key",
                KeyProperties.PURPOSE_ENCRYPT |
                        KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build());
    } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
            | CertificateException | IOException e) {
        return false;
    }
    return true;

}

This is based on the following Android keystore docs statement:

  • User authentication authorizes a specific cryptographic operation associated with one key. In this mode, each operation involving such a key must be individually authorized by the user. Currently, the only means of such authorization is fingerprint authentication: FingerprintManager.authenticate. Such keys can only be generated or imported if at least one fingerprint is enrolled (see FingerprintManager.hasEnrolledFingerprints). These keys become permanently invalidated once a new fingerprint is enrolled or all fingerprints are unenrolled.

See the "Require user authentication for key use" section here https://developer.android.com/training/articles/keystore

algrid
  • 5,600
  • 3
  • 34
  • 37
  • This can't be used to check biometric since the user authentication also works with pattern/pin/password. – slhddn Feb 05 '19 at 19:26
  • @slhddn have you tried it? The key point is to use user authentication with `KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT` here. – algrid Feb 11 '19 at 10:27
  • I've seen this part "These keys become permanently invalidated once a new fingerprint is enrolled or all fingerprints are unenrolled". But then how do you manager previous keys once a new fingerprint is enrolled if they all get invalidated? – stdout Jul 01 '19 at 12:12
  • I believe that the right way is to catch all keyGenerator.init() and cipher.init() exceptions and implement appropriate fallback modes. The point is: There are so many possible cases and exceptions that you cannot rely on checks like the FingerprintManager API. – Mike76 Jul 15 '19 at 11:12
6

I wrote this method for Kotlin:

fun checkForBiometrics() : Boolean{
    Log.d(TAG, "checkForBiometrics started")
    var canAuthenticate = true
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (Build.VERSION.SDK_INT < 29) {
            val keyguardManager : KeyguardManager = applicationContext.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
            val packageManager : PackageManager   = applicationContext.packageManager
            if(!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
                Log.w(TAG, "checkForBiometrics, Fingerprint Sensor not supported")
                canAuthenticate = false
            }
            if (!keyguardManager.isKeyguardSecure) {
                Log.w(TAG, "checkForBiometrics, Lock screen security not enabled in Settings")
                canAuthenticate = false
            }
        } else {
            val biometricManager : BiometricManager = this.getSystemService(BiometricManager::class.java)
            if(biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS){
                Log.w(TAG, "checkForBiometrics, biometrics not supported")
                canAuthenticate = false
            }
        }
    }else{
        canAuthenticate = false
    }
    Log.d(TAG, "checkForBiometrics ended, canAuthenticate=$canAuthenticate ")
    return canAuthenticate
}

Additional, you have to implement on you app gradle file as dependecy:

implementation 'androidx.biometric:biometric:1.0.0-alpha04'

and also use the newest build tools:

compileSdkVersion 29
buildToolsVersion "29.0.1"
markomoreno
  • 318
  • 3
  • 5
2

In my biometrics, I used these and a few more checks to make sure that the device was capable and the fingerprint was enabled. In Kotlin I created an Object class and called it utilities.

object BiometricUtilities {
    fun isBiometricPromptEnabled(): Boolean {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
    }
    fun isSdkVersionSupported(): Boolean {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
    }
    fun isHardwareSupported(context: Context): Boolean {
        val fingerprintManager = FingerprintManagerCompat.from(context)
        return fingerprintManager.isHardwareDetected
    }
    fun isFingerprintAvailable(context: Context): Boolean {
        val fingerprintManager = FingerprintManagerCompat.from(context)
        return fingerprintManager.hasEnrolledFingerprints()
    }
}

Then in my bioManager class, I placed the conditional statements under a function called authenticate that implements BiometricCallback

fun authenticate(biometricCallback: BiometricCallback) {

    if (!BiometricUtilities.isHardwareSupported(context)) {
        biometricCallback.onBiometricAuthenticationNotSupported()
        return
    }

    if (!BiometricUtilities.isFingerprintAvailable(context)) {
        val intent = Intent(Settings.ACTION_SECURITY_SETTINGS)
           biometricCallback.onBiometricAuthenticationNotAvailable()
        return
    }

    displayBiometricDialog(biometricCallback)
}

This way you can check for availability of hardware and presence of fingerprint on the device and let the OS help you to display a prompt or not

EPAgg
  • 99
  • 1
  • 12
1

The method - checks that the user has biometric authentication permission enabled for the app before using the package manager to verify that fingerprint authentication is available on the device. And even it will check if the user is enrolled or not.

implementation 'androidx.biometric:biometric:1.0.0-alpha03'

private Boolean checkBiometricSupport() {

    KeyguardManager keyguardManager =
            (KeyguardManager) getSystemService(KEYGUARD_SERVICE);

    PackageManager packageManager = this.getPackageManager();

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        notifyUser("This Android version does not support fingerprint authentication.");
        return false;
    }

    if(!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
    {
        notifyUser("Fingerprint Sensor not supported");
        return false;
    }

    if (!keyguardManager.isKeyguardSecure()) {
        notifyUser("Lock screen security not enabled in Settings");

        return false;
    }

    if (ActivityCompat.checkSelfPermission(this,
            Manifest.permission.USE_BIOMETRIC) !=
            PackageManager.PERMISSION_GRANTED) {
        notifyUser("Fingerprint authentication permission not enabled");

        return false;
    }

    return true;
}
Twinkle
  • 65
  • 8
0

There is class method available FingerprintManagerCompat.from(this).isHardwareDetected androidx.core.hardware.fingerprint package.

CoDe
  • 11,056
  • 14
  • 90
  • 197
0

For those who do not want to wait for support library released, you can use nightly build like this

repositories {
        maven {
            url "https://ci.android.com/builds/submitted/5795878/androidx_snapshot/latest/repository/"
        }
    }

implementation group: 'androidx.biometric', name: 'biometric', version: '1.0.0-SNAPSHOT'

get build version from here

https://ci.android.com/builds/branches/aosp-androidx-master-dev/

branch aosp-androidx-master-dev

show latest build from androidx-snapshot

ksc91u
  • 520
  • 2
  • 6