we are currently facing a weird BiometricPrompt
behaviour on some Android devices.
The problem is brand/manufacturer independent because it works e.g. on some Samsung devices but on others doesn't.
To approve some transaction made by the user to her account we require her to authenticate herself using her device credentials or biometry.
Under the hood we are using BiometricPrompt
.
The problem we are facing is that immediately after calling the BiometricPrompt.authenticate()
method the device credential screen is shown (specifically only PIN), our underlying activity gets destroyed (there are just some milliseconds in between), we enter the Pin-Code, click continue, the device credential screens disappears and we see the home screen of the device itself.
The problem only occurs while the users has set up a PIN as device credential.
Pattern and biometry are working fine.
Also, PIN is working on some other Android devices.
Here is the code we are calling:
suspend fun authenticate(
activity: FragmentActivity
): AuthenticationResult = suspendCoroutine { continuation ->
@Suppress("DEPRECATION") // We are aware of this. If possible we use the "Modern" approach.
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(R.string.biometric_prompt_title))
.setDeviceCredentialAllowed(true)
.build()
activity.createBiometricPrompt(continuation).authenticate(promptInfo)
}
Our authenticate()
method is called on the main thread using Dispatchers.Main
.
Here is the code of the createBiometricPrompt()
method:
fun FragmentActivity.createBiometricPrompt(continuation: Continuation<AuthenticationResult>): BiometricPrompt {
return BiometricPrompt(
this,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
continuation.resume(AuthenticationResult.Error(errorCode, errString))
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
continuation.resume(AuthenticationResult.Succeeded(result))
}
override fun onAuthenticationFailed() {
Timber.i("onAuthenticationFailed")
}
}
)
}
Since we are sending a push to the device which is registered as the second factor we create a PendingIntent
to present the transaction activity.
We create the activity with the following code:
fun createPendingIntent(context: Context, transaction: Transaction): PendingIntent =
Intent(context, TransactionActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
putExtra(INTENT_EXTRA_TRANSACTION_APPROVAL, transaction)
}.let { intent ->
val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.getActivity(context, 1484, intent, flags)
}
We use the same code during login.
There our code works without any issues on all devices.
Therefore my guess that we might do something wrong creating the Intent
/PendingIntent
? 🤷
Does any of you has an idea what might cause our problem?
onDestroy
of our activity is calledflags
from Intent
I just found a solution to my problem.
Regarding the documentation here Start an Activity from a Notification they recommend using TaskStackBuilder
to build your PendingIntent
.
Using TaskStackBuilder
our activity isn't killed while the PIN screen gets/is shown.