Determine if biometric hardware is present and the user has enrolled biometrics on Android P
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)
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
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