Change theme
Help
Press space for more information.
Show links for this issue (Shortcut: i, l)
Copy issue ID
Previous Issue (Shortcut: k)
Next Issue (Shortcut: j)
Sign in to use full features.
Vote: I am impacted
Notification menu
Refresh (Shortcut: Shift+r)
Go home (Shortcut: u)
Pending code changes (auto-populated)
View issue level access limits(Press Alt + Right arrow for more information)
Unintended behavior
View staffing
Description
Version used: 1.0.0-alpha02
Devices/Android versions reproduced on: Samsung SM-G965U Android 9
This one is being reported only in the wild, and we haven't reproduced it yet. The issue seems to be that the androidx.biometric library can sometimes disobey the @Nonnull annotation in the callback, leading to an internal Kotlin exception when the Java -> Kotlin boundary is crossed.
We have been seeing exception traces of the following type in our application:
Fatal Exception: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.d.b.j.b, parameter errString
at com.varomoney.varo.system.repository.BiometricsRepository$makeAuthenticationCallback$2.$onSuccess(Unknown Source:2)
at androidx.biometrics.BiometricFragment$1$1.run(BiometricFragment.java:66)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6981)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
When looking at our Kotlin code, we have:
BiometricPrompt.AuthenticationCallback {
return object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess(result.cryptoObject?.cipher)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Timber.e("onAuthenticationError $errorCode $errString")
onError(AuthenticationError(errorCode, errString))
}
override fun onAuthenticationFailed() {
Timber.v("onAuthenticationFailed")
}
}
}
The exception is happening on:
override fun onAuthenticationError(errorCode: Int, errString: CharSequence)
Which is implemented in the base Java class:
public void onAuthenticationError(@BiometricError int errorCode,
@NonNull CharSequence errString) {}
So, errString should never be null. However, probably due to something interesting inside, the Samsung implementation of biometrics in Android 9 (this is only a guess and not meant to be disparaging), sometimes, the errString that comes back IS null. In Java this just gets send on down to Kotlin, which catches it as an error.
What I've had to do is make a Java class like the following:
import androidx.annotation.Nullable;
import androidx.biometrics.BiometricPrompt;
/**
* This class is here to allow the errString to be nullable because the library ignores its own
* annotations and sends something that might be null.
*/
public abstract class AuthenticationCallback extends BiometricPrompt.AuthenticationCallback {
@Override
public void onAuthenticationError(int errorCode, @Nullable CharSequence errString) {
}
}
This allows me to change my code to:
private fun makeAuthenticationCallback(
onSuccess: (Cipher?) -> Unit,
onError: (error: AuthenticationError) -> Unit = {}
): BiometricPrompt.AuthenticationCallback {
return object : AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess(result.cryptoObject?.cipher)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
Timber.e("onAuthenticationError $errorCode $errString")
onError(AuthenticationError(errorCode, errString))
}
override fun onAuthenticationFailed() {
Timber.v("onAuthenticationFailed")
}
}
}
And my AuthenticationError class to:
data class AuthenticationError(val errorCode: Int, val errorString: CharSequence?)
We don't use the errorString anyway, as the errorCode is sufficient. So this will prevent, I would hope, the Kotlin code from complaining about a null being sent for errorString.
Hope this helps.