Is there a way to test programatically that my phone's fingerprint sensor, works correctly?
I would like to be able to perform the test automatically, without enrolling my fingerprint and then trying to login with it to see if the sensor is working.
SDK 23 +
fun isHardwareSupported(context: Context): Boolean {
val fingerprintManager = FingerprintManagerCompat.from(context)
return fingerprintManager.isHardwareDetected
}
SDK 28+
fun isHardwareSupported(context: Context): Boolean {
val pm = context.packageManager
return pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
}
Another alternative is to use BiometricManager androidx Library
The method can be used to determine if biometric hardware is present and if the user is enrolled or not.
fun isHardwareSupported(context: Context): Boolean {
return BiometricManager.from(context).canAuthenticate() != BIOMETRIC_ERROR_HW_UNAVAILABLE)
}
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). Value is BIOMETRIC_SUCCESS, BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, or BIOMETRIC_ERROR_NO_HARDWARE
Related
I'm working on a Wifi auto connect feature and I am shocked how broken that API is.
I'm using now 5 different APIs and I still don't get it in a way the user would expect it.
I have a setting to enable wifi auto connection on Android 10+ I'll try this:
Check if I hold the ACCESS_WIFI_STATE permission with:
appOpsManager.unsafeCheckOp("android:change_wifi_state", Process.myUid(), context.packageName) == AppOpsManager.MODE_ALLOWED
When I own the permission I go on with 2. if not I continue with 6.
When I hold the permission I'll check on Android 11+ if from a previous run my wifi suggestion was saved wifiManager.networkSuggestions.isNotEmpty() if that is true I check which Wifi I'm currently connected with see step x. On lower levels I skip this and go to step 3
I use the wifi suggestion API with the WifiNetworkSuggestion.Builder() and suggest the user my wifi with
val status = wifiManager.addNetworkSuggestions(listOf(suggestion))
// Strage bug: On first usage the return value is always SUCCESS
val success = status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS
When the user accepts my suggestion I'm fine so far. I cannot verify on Android 10 if my suggestion was accepted. On Android 11 this is possible (see step 2). However I still don't know if the device actually connected with the wifi. So I go to step 7
When the user rejected my suggestion I'm almost out of the game I can never ask the user again (well technically there is a well hidden feature where you can switch the permission, but no user will ever find it). On Android 10 I can just check if I'm connected with the right wifi, well at least theoretically (see step 7).
On Android 11+ I can fallback to the Settings.ACTION_WIFI_ADD_NETWORKS intent which can always add Wifis (with the downside that the user can share the wifi password)
I need to verify if the steps before worked and the user is actually connected to the desired wifi. To do so I already own the permissions ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE, ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION. However the ConnectivityManager API don't return the actual SSID, that drives me crazy.
If the user is not connected to the right wifi I would try to use the Settings.Panel.ACTION_WIFI action to let the user to connect to the right wifi
To summarize that:
I use the WifiNetworkSuggestion API
I fallback to Settings.ACTION_WIFI_ADD_NETWORKS where possible
I try to verify if the user is currently connected to the right wifi
I try to assist the user to conenct to the right wifi (or even turn wifi on) with the Settings.Panel.ACTION_WIFI action
Is that really such a mess or is there a easier way?
I'm currently accessing the SSID like this:
private val currentSsid: String?
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val wifiInfo = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)?.transportInfo as? WifiInfo
wifiInfo?.ssid
} else {
wifiManager.connectionInfo?.ssid
}
Based on Unable to get WIFI SSID using onCapabilitiesChanged in Android 12 I think that way I access the value is not supported. I get currently the value "<unknown ssid>".
Well just a half answer, but it might help anyway. Here is how I get the current SSID of the user (you need to hold the location permission):
fun updateSsid(networkCapabilities: NetworkCapabilities) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
currentSsid = (networkCapabilities.transportInfo as? WifiInfo)?.ssid?.trim('"')
}
}
wifiCallback = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
object: ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
updateSsid(networkCapabilities)
}
}
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
object: ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
updateSsid(networkCapabilities)
}
}
}
else -> null
}
wifiCallback?.let {
fragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
connectivityManager.registerNetworkCallback(request, it)
}
override fun onPause(owner: LifecycleOwner) {
connectivityManager.unregisterNetworkCallback(it)
}
})
}
So basically aspects of your code need to check apis for the levels <10, 10, 11 and 12. What a mess.
This might be quiet handy if you want the user to pick the configured wifi:
fun showWifiDialog() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
fragment.startActivity(Intent(android.provider.Settings.Panel.ACTION_WIFI))
} catch (e: Exception) {
// ignored
}
}
}
I'm setting up a biometric login using kotlin. I have it working but I would like to switch out which icon is displayed based on the available hardware, ie. show a retinal scanner icon for retinal scanning, fingerprint for fingerprint scanning etc.
So far digging through the docs I haven't been able to find a way to determine this and the google machine hasn't been very useful.
something along the lines of
when (biometricManager.biometricType) {
face -> {}
fingerprint -> {}
retinaScanner -> {}
}
would be awesome. Does this exist?
I figured it out, you use the package manager.
enum class BiometricType {
Iris, Fingerprint, Face, None
}
fun biometricType(context: Context): BiometricType {
return when {
context.applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE) -> BiometricType.Face
context.applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) -> BiometricType.Fingerprint
context.applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS) -> BiometricType.Iris
else -> BiometricType.None
}
}
Hopefully this helps someone else out in the future
I want to use fingerprint auth in my app but, I got problem in Marsmallow android, here is my code
public static boolean isFingerprintAvailable(Context context) {
FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
return fingerprintManager.hasEnrolledFingerprints();
}
always return false even though I already registered a fingerprint on my marsmallow android phone.
I'm writing a application feature to authenticate user using Biometric fingerprint authentication API. And it worked as expected with combination of BiometricPrompt API.
In general it display own UI dialog so it can be unified across Android device.(Improvement from Fingerprint Manager API)
In one of device testing scenario I come across in-display(on screen, e.g. Oneplus 6T device) fingerprint support instead rear biometric hardware option.
When I run application on it, on call of biometricPrompt.authenticate(..) instead of dialog it display in-display fingerprint authentication option. Which is ok, and manage by internal API of BiometricPrompt.
But it create some inconsistency to manage for developer.
When it provide in-build authentication dialog, all fallback error displayed in dialog itself.
But in case of in-display authentication I didn't found a way where it manage to display error message it-self. And I have to handle this fallback and display in a custom way.
Now question is
Is there a way to manage/display message by in-display authentication view component.
How can identify if device is support in-device biometric authentication.
Edit: Code reference I'm using:
import android.content.Context
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import java.lang.Exception
import java.util.concurrent.Executors
import javax.crypto.Cipher
class BiometricAuthenticationManager(private val context: Context) :
BiometricPrompt.AuthenticationCallback() {
private var biometricPrompt: BiometricPrompt? = null
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
biometricPrompt?.cancelAuthentication()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
}
fun init(cipher: Cipher, promptInfo: BiometricPrompt.PromptInfo) {
if (context is FragmentActivity) {
val executor = Executors.newSingleThreadExecutor()
biometricPrompt = BiometricPrompt(context, executor, this)
biometricPrompt?.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
} else {
throw Exception(
"Check for FragmentActivity context.")
}
}
}
For further reference about how it look like, please find following attachment.
I try to check same scenario for lock screen, where I guess it uses custom implementation using FingerPrintManager class and display message.
Faced with the same problem some time ago - OnePlus 6T has no Biometric UI, at the same time Samsung A51 - has its own (custom) UI for Fingerprint API and another for BiometricPrompt API.
I tried to watch for Activity window focus loss (For OnePlus - activity do not lose focus, at the same time BiometricPrompt UI leads to focus loss), but appear a problem with the Samsung device.
The only solution that I found, and it works for now:
Need to get the correct device name (for OnePlus for example, it should be not ONEPLUS A6003, but One Plus 6 instead)
Need to perform request to https://m.gsmarena.com/res.php3?sSearch=Device+Name, you should get the list with matches, usually, first one is needed
Parse HTML (from previews step) to get the link for device specification (like https://m.gsmarena.com/oneplus_6t-9350.php)
Now you need to perform a request to this link and again parse HTML and get the "Sensors" section. You should get a string like "Fingerprint (under display, optical), accelerometer, gyro, proximity, compass"
Split this string to get the list with sensors
Now when you need to check if an in-display scanner is present on the device, just check for "fingerprint" and "under display" for the same sensor.
Link with impl. that can be used as an example
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//Fingerprint API only available on from Android 6.0 (M)
FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
if (!fingerprintManager.isHardwareDetected()) {
// Device doesn't support fingerprint authentication
} else if (!fingerprintManager.hasEnrolledFingerprints()) {
// User hasn't enrolled any fingerprints to authenticate with
} else {
// Everything is ready for fingerprint authentication
}
}
Dont Forget to Add
<uses-permission android:name=" android.permission.USE_BIOMETRIC" />
As we all know, from android 9.0, android introduced BiometricPrompt Api to provide standard authentication experience across growing range of biometric sensors (E.g Fingerprint,Face ID etc).
Now with this new BiometricPrompt Api user can get authenticated via fingerprint, face scanner or iris scanned (depend on their biometric preference). BiometricPrompt api will take care of this and it will notify us via various callbacks.
Below is my code to display Biometric Prompt.
biometricPrompt = new BiometricPrompt.Builder(context)
.setTitle("FingerPrint Authentication")
.setSubtitle("Login via Fingerprint")
.setDescription("Touch Fingerprint Sensor")
.setNegativeButton("Cancel", context.getMainExecutor(),
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d(TAG,"Cancelled");
}
})
.build();
Now if you see my code, i am setting title as a Fingerprint Authentication. Now in device setting, if user had set a Biometric Preference as a Face ID instead of FingerPrint then this biometricPrompt will authenticate user via faceID and fingerprint sensor wont work even if user keep touching sensor. This would create confusion as Biometric title is saying that "Fingerprint authentication" and user is actually getting authenticated via faceID
Is there any way by which we can know what Biometric preference user has selected (e.g Fingerprint or FaceID)? So based upon that preference i can show appropriate message on BiometricPrompt so user wont get confused.
I already explored all api from BiometricPrompt but could find anything related to BiometricPreference.
Any help would be highly appreciated.
While not a perfect solution, you can use the PackageManager API to determine whether a device has the authenticator hardware, e.g.:
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
{
}
I've created a helper class as follows:
class BiometricAuthenticator
{
public enum BiometricType
{
FINGERPRINT,
FACE,
IRIS,
NONE
}
public static boolean hasBiometricAuthenticator(Context context)
{
int biometry = BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED;
if (VERSION.SDK_INT >= 30)
biometry = BiometricManager.from(context).canAuthenticate(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK);
else
biometry = BiometricManager.from(context).canAuthenticate();
switch (biometry)
{
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
return (false);
case BiometricManager.BIOMETRIC_SUCCESS:
return true;
}
return (false);
}
/**
* biometricType()
*
* returns type of biometry supported
*/
public static BiometricType biometricType(Context context)
{
if (VERSION.SDK_INT < 23)
return BiometricType.NONE;
PackageManager packageManager = context.getPackageManager();
// SDK 29 adds FACE and IRIS authentication
if (VERSION.SDK_INT >= 29)
{
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
return BiometricType.FACE;
if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS))
return BiometricType.IRIS;
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
return BiometricType.FINGERPRINT;
return BiometricType.NONE;
}
// SDK 23-28 offer FINGERPRINT only
return (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) ? BiometricType.FINGERPRINT : BiometricType.NONE);
}
}
This allows you to determine if a biometric authenticator is present (hasBiometricAuthenticator), and if so, return the type of authenticator as a BiometricType enum.
A device could theoretically have multiple authenticators, and biometricType() will return FACE, IRIS, then FINGERPRINT in that order of preference on API30+ devices.
Hopefully Google will expose better API in the future, but these tricks will at least help get appropriate prompts on the dialog
There is no mean of knowing this type of information for now, an issue had been opened last year to ask for it (https://issuetracker.google.com/issues/111315641). As Android tried to simplify the path for developer to implement authentication in their apps, there is a lack of options in the BiometricPrompt implementation (see the Android document for BiometricPrompt implementation).
In your case you can simply change your title String to "Biometric Authentication", and so with the other Strings. For an example see the blog posts pointed to below.
Your code might look as follows. But I'd also recommend you use the strings.xml resource file instead of hard-coding your Strings in the code. For example, in the future, you may want translation services.
biometricPrompt = new BiometricPrompt.Builder(context)
.setTitle("Biometric Authentication")
.setSubtitle("Login via biometrics")
.setDescription("Use the Biometrics Sensor")
.setNegativeButton("Cancel", context.getMainExecutor(),
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d(TAG,"Cancelled");
}
})
.build();
More broadly, the privacy implications must be evaluated before the API team makes a decision whether developers should know a user's biometric preferences. It's really not clear why a developer should need this information. Two blog posts have been published that touch on the subject of Strong vs Weak biometrics and how to implement them (blog one, blog two). Beyond that distinction (i.e. Strong vs Weak), what form-factors a user prefers or ends up using doesn't seem pertinent.
In Android R was added a method called setAllowedAuthenticators
public BiometricPrompt.Builder setAllowedAuthenticators (int authenticators)
Optional: Specifies the type(s) of authenticators that may be invoked by BiometricPrompt to authenticate the user. Available authenticator types are defined in Authenticators and can be combined via bitwise OR. Defaults to:
Authenticators#BIOMETRIC_WEAK for non-crypto authentication, or
Authenticators#BIOMETRIC_STRONG for crypto-based authentication.
If this method is used and no authenticator of any of the specified types is available at the time BiometricPrompt#authenticate(...) is called, authentication will be canceled and AuthenticationCallback#onAuthenticationError(int, CharSequence) will be invoked with an appropriate error code.
This method should be preferred over setDeviceCredentialAllowed(boolean) and overrides the latter if both are used. Using this method to enable device credential authentication (with Authenticators#DEVICE_CREDENTIAL) will replace the negative button on the prompt, making it an error to also call setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener).
authenticators
A bit field representing all valid authenticator types that may be
invoked by the prompt. Value is either 0 or a combination of
BiometricManager.Authenticators.BIOMETRIC_STRONG,
BiometricManager.Authenticators.BIOMETRIC_WEAK, and
BiometricManager.Authenticators.DEVICE_CREDENTIAL