Advanced BiometricPromptCompat
====
Apache License 2.0
Telegram: @SergeyKomlach
Twitter: @SergejKomlach
This is a new API that declares that the system takes care of a unified way to use different biometric identification methods - fingerprint, face, iris, etc., as well as a unified way to display the UI on all devices.
To learn more, read BiometricPrompt Reference on the Android Developers site.
Unfortunately, this simplification hides a number of problems.
- On Android 9, there is no way to simply get information about whether there is available biometric hardware and whether biometric data is enrolled. Android 10 provides BiometricManager that partially solves this problem.
- Some manufacturers have removed/do not display the biometric UI
- Biometric Auth solutions like Samsung Pass SDK or Meizu Fingerprint need to implement separately
- No way to identify what types of biometric auth available on the device.
- On Android 12 and devices with FaceUnlock (like Pixel 4), when user disable Camera via QuickSettings, Face setup and FaceUnlock stop working and no API to handle this case
BiometricPromptCompat is designed to be compatible with the largest number of Android devices.
Its interface is very close to the original BiometricPrompt
.
Minimal supported Android OS version: Android 4.1 Jelly Bean
Latest supported Android OS version: Android 15 VanillaIceCream
- Unified UI for all devices. Exception: some vendors (like Huawei or Samsung) provide custom UI
- Contains fix for devices WITHOUT system BiometricPrompt UI (like LG G8 or OnePlus 6T)
- Dark/Light themes supported; Also you able to get the background color of the current Biometric dialog
- Auth in Split-Screen Mode supported
- Wide range of supported biometrics
- Android 12+ microphone and camera toggles handling
- DynamicColors (MaterialYou/Monet) theming supported
Type | Details | Tested on |
---|---|---|
BiometricPrompt API | Android 9+ | Xiaomi POCO F1, OnePlus 8T |
Samsung IrisID | Android 7+ and Samsung devices | Samsung Galaxy S10 |
Samsung Pass Fingerprint | Android 4.4-6.0 and Samsung devices | Samsung Galaxy S5 |
Fingerprint | Android 6 - 13 | Xiaomi POCO F1 |
In-screen Fingerprint | Android 8+ | OnePlus 6T/OnePlus 7 Pro |
Meizu Fingerprint | Android 5.0-5.1 and Meizu devices | Meizu Pro 5 |
Face Unlock (aka TrustedFaces) | Android 4.1+ | Prestigio PAP3400 |
Huawei/Honor FaceID | Android 8 - 9 and Huawei devices | Huawei/Honor 8 Lite, Honor Magic 5 Lite |
Huawei/Honor Face3D | Android 10+ and Huawei devices | Huawei MatePad T8, Huawei P30,Huawei Mate 30 Pro |
Xiaomi FaceUnlock | Android 7+ and Xiaomi devices | Xiaomi POCO F1 |
Samsung FaceID | Android 7+ and Samsung devices | Samsung Galaxy S10 |
Oppo FaceID | Android 8+ and Oppo devices | Not tested yet |
Lava FaceId | Android (Unknown) and Lava devices | Not tested yet |
Windows Subsystem for Android & Windows Hello | Doesn't work; Stubs in system API's | Acer Aspire 7 with fingerprint scanner & Windows 11 |
November 12 2024
Bugfixes Xiaomi/Mi devices - fixed bug when multiple biometrics (Finger + Face) doesn't work when set ANY combination
Improvement biometric API's detection aligned with current Android ecosystem state:
-
MIUI FaceID - will work on AOS7 - AOS13 (starting from AOS14 API not accessible)
-
Huawei/Honor legacy FaceUnlock - will work on AOS7 - AOS9 (starting from AOS10 vendor provide official localauthentification.jar library; Starting from AOS10 legacy API behaves unexpectedly)
November 4 2024
Bugfixes Fixed issue with Notification channel request on OneUI 6.1+
October 9 2024
Bugfixes Fixed network connection check
September 14 2024
Bugfixes Fixed bug on Pixel 9 Pro Fold when activity restarted endlessly in unfolded mode
Bugfixes Fixed bug on Remdi Tab and FaciId
Bugfixes Sony Xperia devices:
- fixed init hangs due to inproper FaceUnlock initialization;
- fixed bug when due to HAL/driver issue fingeprint sensor is unreachable on SW level (see https://community.sony.pt/t5/1-series/fingerprint-scanner-option-has-now-disappeared/td-p/3655860)
Redmi Tab: fixed bug when binding to "miui.face.FaceService" fails - in this case hadrware/enroll properly detected, but authenticate
does nothing
Test app fixed cause leads to crashes on some devices
Improvement Moved to Tokens usage during publishing flow
You can check how the library works on your device using this APK
Xiaomi Pocophone F1
Samsung Galaxy S5
Huawei Mate P40 Pro
Prestigio PAP3400
Video from Xiaomi Pocophone F1
Add dependency to Gradle
implementation 'dev.skomlach:biometric:${VERSION}'
Optional
implementation 'dev.skomlach:biometric-ktx:${VERSION}'
BiometricPromptCompat API
BiometricPromptCompat.Companion:
fun getAvailableAuthRequests(): List<BiometricAuthRequest>
- return the list with all Biometrics, supported on this device
var deviceInfo: DeviceInfo?
- return device hardware specifications
For development purpose only:
fun logging(enabled: Boolean)
- allow to enable/disable logging
fun apiEnabled(enabled: Boolean)
- allow to enable/disable this library
BiometricAuthRequest
Allows you to configure the type of target biometrics.
It can be any combination of BiometricApi, BiometricConfirmation and BiometricType;
Default
is BiometricAuthRequest(BiometricApi.AUTO, BiometricType.BIOMETRIC_ANY, BiometricConfirmation.ANY)
BiometricConfirmation:
BiometricConfirmation.ANY
- any biometric confirm the user
BiometricConfirmation.ALL
- all (one-by-one) biometrics confirm the user
BiometricApi:
BiometricApi.AUTO
- the library will peek at the best-matched API
BiometricApi.LEGACY_API
- forced usage of legacy biometric APIs like Fingerprint or FaceUnlock,
and custom UI
BiometricApi.BIOMETRIC_API
- forced usage of new BiometricPrompt API
BiometricType:
BiometricType.BIOMETRIC_FINGERPRINT
- Use only Fingerprint biometric, ignore others
BiometricType.BIOMETRIC_FACE
- Use only FaceId biometric, ignore others
BiometricType.BIOMETRIC_IRIS
- Use only Iris biometric, ignore others
BiometricType.BIOMETRIC_ANY
- use any available biometric (multiple types supported)
BiometricManagerCompat
fun hasEnrolled(): Boolean
- returns true
if specified biometric enrolled
fun isBiometricSensorPermanentlyLocked(): Boolean
- returns true
if:
a) specified biometric permanently locked; Device lock-unlock or reboot required from the user
b) hardware permanently blocked by user
fun isHardwareDetected(): Boolean
- returns true
if specified biometric hardware available
fun isLockOut(): Boolean
- returns true
if
a) specified biometric temporarily locked (Usually need to wait for 30 seconds and the system will reset this lock)
b) hardware temporary locked by 3rd party app
fun openSettings(Activity): Boolean
- returns true
if open the "Enroll biometric" settings
screen for specified biometric
fun registerCustomBiometric(): Boolean
- returns true
provided biometric can be registered. Useful when you need to implement non-hadrware functions like AWS VoiceID etc.
fun isSilentAuthAvailable(): Boolean
- returns true
if silent auth available.
BiometricPromptCompat.Builder
Simplest builder:
val builder = BiometricPromptCompat.Builder(activity).setTitle("Biometric demo") .setNegativeButton("Cancel", null)
val biometricPromptCompat = builder.build()
BiometricPromptCompat:
fun authenticate(BiometricPromptCompat.AuthenticationCallback)
- start biometric
auth workflow
fun cancelAuthentication()
- cancel active biometric auth workflow
@ColorRes fun getDialogMainColor(): Int
- returns dialog background color
BiometricPromptCompat.AuthenticationCallback
fun onSucceeded(Set<BiometricType>)
- User successfully authenticated
fun onCanceled()
- Biometric authentication was canceled
fun onFailed(AuthenticationFailureReason)
- Error happens, see details in *
AuthenticationFailureReason*
fun onUIOpened()/fun onUIClosed
- Biometric UI on display or closed
DeviceInfoManager:
Helper tool to check some biometric-related stuff in device specification
fun hasFingerprint(DeviceInfo): Boolean
fun hasUnderDisplayFingerprint(DeviceInfo): Boolean
fun hasIrisScanner(DeviceInfo): Boolean
fun hasFaceID(DeviceInfo): Boolean
private fun startBioAuth() {
val iris = BiometricAuthRequest(
BiometricApi.AUTO,
BiometricType.BIOMETRIC_IRIS,
BiometricConfirmation.ANY
)
val faceId = BiometricAuthRequest(
BiometricApi.AUTO,
BiometricType.BIOMETRIC_FACE,
BiometricConfirmation.ANY
)
val fingerprint = BiometricAuthRequest(
BiometricApi.AUTO,
BiometricType.BIOMETRIC_FINGERPRINT,
BiometricConfirmation.ANY
)
var title = ""
val currentBiometric =
if (BiometricManagerCompat.isHardwareDetected(iris)
&& BiometricManagerCompat.hasEnrolled(iris)
) {
title =
"Your eyes are not only beautiful, but you can use them to unlock our app"
iris
} else
if (BiometricManagerCompat.isHardwareDetected(faceId)
&& BiometricManagerCompat.hasEnrolled(faceId)
) {
title = "Use your smiling face to enter the app"
faceId
} else if (BiometricManagerCompat.isHardwareDetected(fingerprint)
&& BiometricManagerCompat.hasEnrolled(fingerprint)
) {
title = "Your unique fingerprints can unlock this app"
fingerprint
} else {
null
}
currentBiometric?.let { biometricAuthRequest ->
if (BiometricManagerCompat.isBiometricSensorPermanentlyLocked(biometricAuthRequest)
|| BiometricManagerCompat.isLockOut(biometricAuthRequest)
) {
showToast("Biometric not available right now. Try again later")
return
}
val prompt = BiometricPromptCompat.Builder(this).apply {
this.setTitle(title)
this.setNegativeButton("Cancel", null)
this.setEnabledNotification(false)//hide notification
this.setEnabledBackgroundBiometricIcons(false)//hide duplicate biometric icons above dialog
this.setCryptographyPurpose(BiometricCryptographyPurpose(BiometricCryptographyPurpose.ENCRYPT))//request Cipher for encryption
}
if(!prompt.enableSilentAuth()){
showToast("Unable to use Silent Auth on current device :|")
return
}
prompt.build().authenticate(object : BiometricPromptCompat.AuthenticationCallback {
override fun onSucceeded(confirmed: Set<BiometricType>) {
val encryptedData = CryptographyManager.encryptData(
"Hello, my friends".toByteArray(Charset.forName("UTF-8")),
confirmed
)
showToast("User authorized :)\n Biometric used for Encryption=${encryptedData.biometricType}\n EncryptedData=${encryptedData.data}; InitializationVector=${encryptedData.initializationVector};")
}
override fun onCanceled() {
showToast("Auth canceled :|")
}
override fun onFailed(reason: AuthenticationFailureReason?) {
showToast("Fatal error happens :(\nReason $reason")
}
override fun onUIOpened() {}
override fun onUIClosed() {}
})
} ?: run {
showToast("No available biometric on this device")
}
}
On pure API28 implementation (built-in BiometricPrompt API) is no way to get ' isBiometricEnrolled' results for specific biometric, like Iris/Face, etc. So, some tricks have used that try to determine by indirect signs which biometric data are used (like "if NOT fingerprint, BUT something enrolled in the System Settings").
There are edge cases where we cannot tell exactly what type of biometrics is enrolled - for example,
if it is Samsung with Face and Iris - in this case, the code can give a incorrect result. It can
happen if you set BiometricApi.BIOMETRIC_API + BiometricType.BIOMETRIC_FACE
or BiometricApi.BIOMETRIC_API + BiometricType.BIOMETRIC_IRIS
Fortunately, for Samsung with Face and Iris, the 'legacy' check should work correctly, so for
general cases when you
use BiometricApi.AUTO/LEGACY_API + BiometricType.BIOMETRIC_FACE/BIOMETRIC_IRIS
all should work
fine.
I have a device that can be unlocked using Fingerprint/Face/Iris and(or) I can use this biometric type in pre-installed apps. But it doesn't work on 3rd party apps. Can you help?
Yes, this is, unfortunately, happening very often. Many functions demanded by the market are often implemented by device manufacturers before the same API appears in the official Android SDK.
The device manufacturer has implemented biometric authentication via fingerprint/face/iris, but " forgot" to provide access to this implementation for third-party developers. Therefore, preinstalled (system) applications developed by the device manufacturer can use biometrics, while banking applications, password managers, and other third-party applications cannot.
And unfortunately, sometimes manufacturers create such implementations that it is impossible to access using any known technic.
Anyway, research and testing are required for each case, so feel free to create issues or contact directly with me.
For RELEASE builds you can get in the console warnings like below:
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow. In later version of R8, the method may be assumed not reachable.","sources":[{"file":"~\\biometric-2.2.3-runtime.jar"}],"tool":"R8"}
This warnings - just an information that classes from Samsung_pass-v1.2.6.jar and Samsung_sdk-v1.0.0.jar compiled with Class File Version 50, when recent Gradle expect Class File Version 51
Library still works properly, so, no worries
-
FindBugs
-
Find Security Bugs
-
OWAPS dependencies check
-
Snyk
-
Sonatype-Lift