Fixed
Status Update
Comments
ra...@gmail.com <ra...@gmail.com> #2
Hi Ed, Thank you so much for these suggestions. I've been reviewing them and merging them in. Hopefully it should be live. I've included a thank you note too in the article.
ad...@google.com <ad...@google.com>
ad...@google.com <ad...@google.com> #3
Great! Thanks a lot, I'll look for the live updates soon!
to...@gmail.com <to...@gmail.com> #4
I think this issue needs to be fixed as a first priority. You can reproduce the error with opening app and calling biometricPrompt.authenticate() after that press home button and then, show your finger to sensor. It'll crash instantly. You can reproduce the issue easily. I can do that with my Oreo device. I think that cancelAuthenticate doesn't work properly.
ra...@gmail.com <ra...@gmail.com> #5
This issue looks like fixed with library -- androidx.biometric:biometric:1.0.0-alpha03
Q) Once user tries for 5 times, the popUp will not be visible for next 30 seconds. It is taking too much time to give errorCode=7 (ERROR_LOCKOUT) when tries to see the biometric popup again. Is there a way to see the errorCode faster than usual?
Thanks,
Q) Once user tries for 5 times, the popUp will not be visible for next 30 seconds. It is taking too much time to give errorCode=7 (ERROR_LOCKOUT) when tries to see the biometric popup again. Is there a way to see the errorCode faster than usual?
Thanks,
to...@gmail.com <to...@gmail.com> #6
I'm using alpha03 as well but the bug still remains. I'm facing it right now. You can check the problem when you open dialogfragment via authenticate() and press home button and then showing your finger to sensor.
[Deleted User] <[Deleted User]> #7
I'll attach a crash log to this, we're getting dozens of crashes per day, over 3% of our user sessions. Will try alpha03, and some of the reproduction notes here.
[Deleted User] <[Deleted User]> #8
Here's a recent crashlytics bug report on this, with alpha02.
[Deleted User] <[Deleted User]> #9
Tried alpha03, and I don't have exactly the same issue, a different one has now surfaced with #6's reproduction notes:
2018-12-20 16:54:19.708 9948-9948/com.varomoney.varo.dev E/AndroidRuntime: FATAL EXCEPTION: main
Process:com.varomoney.varo.dev , PID: 9948
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.varomoney.varo.dev/com.varomoney.varo.presentation.launcher.LauncherActivity }: java.lang.IllegalArgumentException: Executor must not be null
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.IllegalArgumentException: Executor must not be null
at android.hardware.biometrics.BiometricPrompt$Builder.setNegativeButton(BiometricPrompt.java:182)
at androidx.biometric.BiometricFragment.onCreate(BiometricFragment.java:201)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2414)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1418)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1861)
at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3269)
at androidx.fragment.app.FragmentManagerImpl.dispatchCreate(FragmentManager.java:3223)
at androidx.fragment.app.FragmentController.dispatchCreate(FragmentController.java:190)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:369)
at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:85)
at com.varomoney.varo.presentation.base.BaseActivity.onCreate(BaseActivity.kt:73)
at com.varomoney.varo.presentation.launcher.LauncherActivity.onCreate(LauncherActivity.kt:59)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
I've looked and I'm passing com.google.android.gms.tasks.TaskExecutors.MAIN_THREAD for the executor in my use of the API.
2018-12-20 16:54:19.708 9948-9948/
Process:
java.lang.RuntimeException: Unable to start activity ComponentInfo{
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.IllegalArgumentException: Executor must not be null
at android.hardware.biometrics.BiometricPrompt$Builder.setNegativeButton(BiometricPrompt.java:182)
at androidx.biometric.BiometricFragment.onCreate(BiometricFragment.java:201)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2414)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1418)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1861)
at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3269)
at androidx.fragment.app.FragmentManagerImpl.dispatchCreate(FragmentManager.java:3223)
at androidx.fragment.app.FragmentController.dispatchCreate(FragmentController.java:190)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:369)
at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:85)
at com.varomoney.varo.presentation.base.BaseActivity.onCreate(BaseActivity.kt:73)
at com.varomoney.varo.presentation.launcher.LauncherActivity.onCreate(LauncherActivity.kt:59)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
I've looked and I'm passing com.google.android.gms.tasks.TaskExecutors.MAIN_THREAD for the executor in my use of the API.
[Deleted User] <[Deleted User]> #10
Sorry I was using a Pixel 3 on that last report. Will try with Oreo.
[Deleted User] <[Deleted User]> #11
If I use alpha03, and set 'Don't keep activities' on, and do the following:
1) Perform an authenticate
2) Hit home button before applying fingerprint
3) Return to the app, and apply fingerprint
I get:
Process:com.varomoney.varo.dev , PID: 8463
java.lang.NullPointerException: Attempt to invoke interface method 'void java.util.concurrent.Executor.execute(java.lang.Runnable)' on a null object reference
at androidx.biometric.FingerprintHelperFragment$1.onAuthenticationSucceeded(FingerprintHelperFragment.java:128)
at androidx.core.hardware.fingerprint.FingerprintManagerCompat$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:176)
at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:961)
at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:882)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Doesn't seem to be remembering the executor that is passed in for some reason. Arguably this is probably a new bug and not the same one as in alpha02.
1) Perform an authenticate
2) Hit home button before applying fingerprint
3) Return to the app, and apply fingerprint
I get:
Process:
java.lang.NullPointerException: Attempt to invoke interface method 'void java.util.concurrent.Executor.execute(java.lang.Runnable)' on a null object reference
at androidx.biometric.FingerprintHelperFragment$1.onAuthenticationSucceeded(FingerprintHelperFragment.java:128)
at androidx.core.hardware.fingerprint.FingerprintManagerCompat$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:176)
at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:961)
at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:882)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Doesn't seem to be remembering the executor that is passed in for some reason. Arguably this is probably a new bug and not the same one as in alpha02.
[Deleted User] <[Deleted User]> #12
Here's another issue:
1) Display authenticate dialog on Oreo. Don't apply fingerprint.
2) Go to developer settings > Running Services > Show Cached Processes, and kill the app in question.
3) Re-launch the app and just tap off the authenticate dialog.
4) App will crash with:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.biometric.BiometricPrompt$AuthenticationCallback.onAuthenticationError(int, java.lang.CharSequence)' on a null object reference
at androidx.biometric.FingerprintHelperFragment.sendErrorToClient(FingerprintHelperFragment.java:268)
at androidx.biometric.FingerprintHelperFragment.cancel(FingerprintHelperFragment.java:224)
at androidx.biometric.FingerprintDialogFragment.onCancel(FingerprintDialogFragment.java:215)
at android.app.Dialog$ListenersHandler.handleMessage(Dialog.java:1364)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
1) Display authenticate dialog on Oreo. Don't apply fingerprint.
2) Go to developer settings > Running Services > Show Cached Processes, and kill the app in question.
3) Re-launch the app and just tap off the authenticate dialog.
4) App will crash with:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.biometric.BiometricPrompt$AuthenticationCallback.onAuthenticationError(int, java.lang.CharSequence)' on a null object reference
at androidx.biometric.FingerprintHelperFragment.sendErrorToClient(FingerprintHelperFragment.java:268)
at androidx.biometric.FingerprintHelperFragment.cancel(FingerprintHelperFragment.java:224)
at androidx.biometric.FingerprintDialogFragment.onCancel(FingerprintDialogFragment.java:215)
at android.app.Dialog$ListenersHandler.handleMessage(Dialog.java:1364)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
[Deleted User] <[Deleted User]> #13
I'll spend some time and make an app that demonstrates these issues so that you can work to tighten up this library.
[Deleted User] <[Deleted User]> #14
I spent some time extracting out a small working example of what I'm seeing with alpha03. There's some sort of issue with callback reuse in there for sure. alpha02 with this example (just have to change a version number and import in BiometricRepository.kt). Hope it helps find some issues.
What I'm seeing when I run the test app:
1) App will begin with an Enroll button showing.
2) Tap the Enroll button.
3) Present fingerprint.
4) Should be able to tap the Login button to authenticate.
5) Login dialog will show.
6) Present fingerprint.
7) App will crash because the callback being used will be the 'Enroll' callback, not the 'Login' callback.
Open App again.
8) Login dialog will show.
9) Present fingerprint
10) Should see Authenticated toast.
11) Tap Login Button
12) Present Fingerprint
13) App will crash because the login callback (from the initial run) is being reused again, and it's stale.
I fully expect I could be doing something wrong with the API. I also apologize in advance, the app is code extracted from a much larger project and I removed all of the Dagger 2, and just brought in what I needed, so it's a bit of a mess.
I really can't explain the behaviour I'm seeing on alpha03, I hope this helps find out what's wrong.
What I'm seeing when I run the test app:
1) App will begin with an Enroll button showing.
2) Tap the Enroll button.
3) Present fingerprint.
4) Should be able to tap the Login button to authenticate.
5) Login dialog will show.
6) Present fingerprint.
7) App will crash because the callback being used will be the 'Enroll' callback, not the 'Login' callback.
Open App again.
8) Login dialog will show.
9) Present fingerprint
10) Should see Authenticated toast.
11) Tap Login Button
12) Present Fingerprint
13) App will crash because the login callback (from the initial run) is being reused again, and it's stale.
I fully expect I could be doing something wrong with the API. I also apologize in advance, the app is code extracted from a much larger project and I removed all of the Dagger 2, and just brought in what I needed, so it's a bit of a mess.
I really can't explain the behaviour I'm seeing on alpha03, I hope this helps find out what's wrong.
ad...@google.com <ad...@google.com> #15
We are able to reproduce this issue according to steps in comment #14 .
kc...@google.com <kc...@google.com> #16
Going forward, can you please for each issue do the following 3 things? I suggest we ignore everything above this comment, since it's pretty much guess work without the following. I suggest you reproduce the issues and for each problem that has different steps to reproduce, attach the 3 items.
1) clear steps to repro
2) full bugreport (not just the stacktrace), "adb bugreport issue.zip"
3) optionally, attach sample code to repro (less necessary if bugreport is there, but is still useful so I can see what you're doing / trying to do)
Comments on the previous comments
Starting on comment #9 since everything else before is obsolete or is anecdotal and does not have useful debugging info
#9) It's possible for your executor to be null, how did you reproduce this issue, was it the same as the steps in comment #11 ? If so, please check that you're re-initializing BiometricPrompt onCreate or onResume. This needs to be done, since executors/callbacks that were existent in the library's fragments could already be GC'd (happens when don't keep activities alive is toggled in developer settings, and also possibly when you leave the app and come back). See the demo app linked here for example.
aosp-androidx/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java
#11) looks like a similar issue, can you try the suggestion above?
#12) I need more info, can you clarify what " tap off the authenticate dialog." means? Can you attach a full bugreport? Bits of stack traces are not enough to understand the issue
1) clear steps to repro
2) full bugreport (not just the stacktrace), "adb bugreport issue.zip"
3) optionally, attach sample code to repro (less necessary if bugreport is there, but is still useful so I can see what you're doing / trying to do)
Comments on the previous comments
Starting on
#9) It's possible for your executor to be null, how did you reproduce this issue, was it the same as the steps in
aosp-androidx/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java
#11) looks like a similar issue, can you try the suggestion above?
#12) I need more info, can you clarify what " tap off the authenticate dialog." means? Can you attach a full bugreport? Bits of stack traces are not enough to understand the issue
[Deleted User] <[Deleted User]> #18
For comment #16 : I would consider most of the flapping before comment #14 pretty much irrelevant. I think that most of it is a combination of poor use of the library itself, and so I made the demo app to show a simple model of what we need this library to be doing correctly.
In alpha03, as long as the library correctly handles fragments, and also correctly handles the callbacks given to it, I believe this bug report can be closed. Though the app attached in comment #14 shows in alpha03, this is not the case.
In alpha03, as long as the library correctly handles fragments, and also correctly handles the callbacks given to it, I believe this bug report can be closed. Though the app attached in
to...@gmail.com <to...@gmail.com> #19
Can you share the sample app you have?
bi...@gmail.com <bi...@gmail.com> #20
Regarding comment #12 : have the same issue on Oreo alpha03 - NPE, which occurs occasionally.
Steps to reproduce:
1) Display authenticate dialog on Oreo. Don't apply fingerprint.
2) Perform 2a) or 2b). Performing 2a) I was able to reproduce issue more often comparing with similar 2b).
2a) Open bunch of heavy apps: youtube, gmail, playstore etc;
2b) Go to developer settings > Running Services > Show Cached Processes, and kill the app in question. ()
3) Re-launch the app and just tap off the authenticate dialog.
4) App will crash with:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.biometric.BiometricPrompt$AuthenticationCallback.onAuthenticationError(int, java.lang.CharSequence)' on a null object reference
In my opinion the reason of AuthenticationCallback == null is that
a) this callback is assigned in 2 places only (see code below);
b) when application is restored after process death, the retained fragment mFingerprintHelperFragment is also restored and sometimes when mLifecycleObserver.onResume() is called (see Place 1):
1) After this call:
mFingerprintHelperFragment = (FingerprintHelperFragment) mFragmentActivity.getSupportFragmentManager().findFragmentByTag(FINGERPRINT_HELPER_FRAGMENT_TAG);
mFingerprintHelperFragment sometimes is null because mFingerprintHelperFragment fragment transaction sometimes is not started yet and FragmentManager doesn't reference such a fragment;
2) Therefore this check returns false and callback is not set:
if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
c) when BiometricPrompt.authenticate() is called (see Place 2), mFingerprintHelperFragment fragment transaction is already started and this check
if (mFingerprintHelperFragment == null)
fails (because mFingerprintHelperFragment is not null) and again callback is not set:
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
androidx.biometric.BiometricPrompt.java
Place 1.
private final LifecycleObserver mLifecycleObserver = new LifecycleObserver() {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
...
mFingerprintHelperFragment = (FingerprintHelperFragment) mFragmentActivity
.getSupportFragmentManager().findFragmentByTag(
FINGERPRINT_HELPER_FRAGMENT_TAG);
if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
Place 2.
private void authenticateInternal(@NonNull PromptInfo info, @Nullable CryptoObject crypto) {
...
if (mFingerprintHelperFragment == null) {
mFingerprintHelperFragment = FingerprintHelperFragment.newInstance();
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
}
Steps to reproduce:
1) Display authenticate dialog on Oreo. Don't apply fingerprint.
2) Perform 2a) or 2b). Performing 2a) I was able to reproduce issue more often comparing with similar 2b).
2a) Open bunch of heavy apps: youtube, gmail, playstore etc;
2b) Go to developer settings > Running Services > Show Cached Processes, and kill the app in question. ()
3) Re-launch the app and just tap off the authenticate dialog.
4) App will crash with:
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.biometric.BiometricPrompt$AuthenticationCallback.onAuthenticationError(int, java.lang.CharSequence)' on a null object reference
In my opinion the reason of AuthenticationCallback == null is that
a) this callback is assigned in 2 places only (see code below);
b) when application is restored after process death, the retained fragment mFingerprintHelperFragment is also restored and sometimes when mLifecycleObserver.onResume() is called (see Place 1):
1) After this call:
mFingerprintHelperFragment = (FingerprintHelperFragment) mFragmentActivity.getSupportFragmentManager().findFragmentByTag(FINGERPRINT_HELPER_FRAGMENT_TAG);
mFingerprintHelperFragment sometimes is null because mFingerprintHelperFragment fragment transaction sometimes is not started yet and FragmentManager doesn't reference such a fragment;
2) Therefore this check returns false and callback is not set:
if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
c) when BiometricPrompt.authenticate() is called (see Place 2), mFingerprintHelperFragment fragment transaction is already started and this check
if (mFingerprintHelperFragment == null)
fails (because mFingerprintHelperFragment is not null) and again callback is not set:
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
androidx.biometric.BiometricPrompt.java
Place 1.
private final LifecycleObserver mLifecycleObserver = new LifecycleObserver() {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
...
mFingerprintHelperFragment = (FingerprintHelperFragment) mFragmentActivity
.getSupportFragmentManager().findFragmentByTag(
FINGERPRINT_HELPER_FRAGMENT_TAG);
if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
Place 2.
private void authenticateInternal(@NonNull PromptInfo info, @Nullable CryptoObject crypto) {
...
if (mFingerprintHelperFragment == null) {
mFingerprintHelperFragment = FingerprintHelperFragment.newInstance();
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
}
ch...@gmail.com <ch...@gmail.com> #21
#20 thanks so much for your comment. This perfectly describes all the issues I've been seeing too, I just hadn't taken the time to make a write up, I'm bad at these things.
Let's get this fixed! These are some serious fragment bugs.
Let's get this fixed! These are some serious fragment bugs.
ja...@gmail.com <ja...@gmail.com> #22
My temporary workaround: remove all biometrics fragments when activity is onPause()
```
override fun onPause() {
// WORKAROUND forhttps://issuetracker.google.com/issues/121117380
supportFragmentManager.fragments.filter { it is BiometricFragment || it is FingerprintDialogFragment }.forEach {
supportFragmentManager.beginTransaction().remove(it).commitNow()
}
super.onPause()
}
```
```
override fun onPause() {
// WORKAROUND for
supportFragmentManager.fragments.filter { it is BiometricFragment || it is FingerprintDialogFragment }.forEach {
supportFragmentManager.beginTransaction().remove(it).commitNow()
}
super.onPause()
}
```
jo...@ori.berlin <jo...@ori.berlin> #23
My workaround is similar to the one in #22. But I had to remove FingerprintHelperFragment as well. I'm calling l this in my activity's onPause() AND before I create a new BiometricPrompt:
```
fun FragmentManager.removeBiometricFragments() {
fragments.filter {
it is BiometricFragment || it is FingerprintDialogFragment || it is FingerprintHelperFragment
}.forEach {
beginTransaction().remove(it).commitNow()
}
}
```
```
fun FragmentManager.removeBiometricFragments() {
fragments.filter {
it is BiometricFragment || it is FingerprintDialogFragment || it is FingerprintHelperFragment
}.forEach {
beginTransaction().remove(it).commitNow()
}
}
```
jo...@ori.berlin <jo...@ori.berlin> #24
I had to change my function from # 23 to this as the fragments are sometimes detached:
```
fun FragmentManager.removeBiometricFragments() {
findFragmentByTag("FingerprintDialogFragment")?.let {
(it as DialogFragment).dismiss()
}
findFragmentByTag("FingerprintHelperFragment")?.let {
beginTransaction().remove(it).commitNow()
}
findFragmentByTag("BiometricFragment")?.let {
beginTransaction().remove(it).commitNow()
}
}
```
```
fun FragmentManager.removeBiometricFragments() {
findFragmentByTag("FingerprintDialogFragment")?.let {
(it as DialogFragment).dismiss()
}
findFragmentByTag("FingerprintHelperFragment")?.let {
beginTransaction().remove(it).commitNow()
}
findFragmentByTag("BiometricFragment")?.let {
beginTransaction().remove(it).commitNow()
}
}
```
gi...@googlemail.com <gi...@googlemail.com> #25
kc...@google.com <kc...@google.com> #26
Josh is working on a fix for a separate but similar bug. This should be fixed shortly.
sa...@stcpay.com.sa <sa...@stcpay.com.sa> #27
Any progress on the issue? The workaround from #24 is not working on M, N and O in some cases. P is ok.
Besides, the workaround from #24 needs improvements. Before removing fragments;
fragment.setRetainInstance(false);
to avoid state saving for the biometric fragments.
Besides, the workaround from #24 needs improvements. Before removing fragments;
fragment.setRetainInstance(false);
to avoid state saving for the biometric fragments.
kc...@google.com <kc...@google.com> #28
This should fixed in alpha04 (not yet released), there were a few changes that addressed fragment issues.
me...@gmail.com <me...@gmail.com> #29
Spam
ph...@gmail.com <ph...@gmail.com> #30
Run
ph...@gmail.com <ph...@gmail.com> #31
Run
ph...@gmail.com <ph...@gmail.com> #32
Run
[Deleted User] <[Deleted User]> #33
The issue still persist on screen rotation!!
[Deleted User] <[Deleted User]> #34
Also please check null before invoking internal methods.
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getString(int)' on a null object reference
at androidx.biometric.BiometricFragment$2$1.run(BiometricFragment.java:86)
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:7045)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getString(int)' on a null object reference
at androidx.biometric.BiometricFragment$2$1.run(BiometricFragment.java:86)
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:7045)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
Description
Devices/Android versions reproduced on: Android P, and also Android M-O
Sometimes, when the Biometric dialogs are displayed, and then the app is exited (with back button), the code inside the various fragments, BiometricFragment, FingerprintHelperFragment, and FingerprintDialogFragment doesn't properly clean up.
For example this code in FingerprintHelperFragment:
/**
* Remove the fragment so that resources can be freed.
*/
void cleanup() {
if (getActivity() != null) {
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
}
}
can be instrumented with a breakpoint, and shown that sometimes getActivity() is null, and so the fragment transaction to remove the fragment never happens. So, the next time the app tries to authenticate with the BiometricPrompt, the app will crash because the fragment has already been added.
The exception on Android M-O looks like:
java.lang.IllegalStateException: Fragment already added: FingerprintDialogFragment{8d6c61c #3 FingerprintDialogFragment}
at androidx.fragment.app.FragmentManagerImpl.addFragment(FragmentManager.java:1916)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:765)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
at androidx.fragment.app.FragmentManagerImpl$1.run(FragmentManager.java:733)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
and on Android P it will reference BiometricFragment instead.
Attempts to search for and remove fragments at runtime, before a Biometric prompt operation were attempted:
val fm = (context as FragmentActivity).supportFragmentManager
fm.fragments.find {
it is BiometricFragment || it is FingerprintDialogFragment
}.also { fragment ->
fragment?.let {
fm.beginTransaction().remove(it).commitAllowingStateLoss()
}
}
However, then the exception on Android M-O mentions FingerprintHelperFragment instead.
Which led to an attempt to mitigate this exception:
fm.fragments.find {
it is FingerprintHelperFragment
}.also { fragment ->
fragment?.let { fm.beginTransaction().remove(it).commitAllowingStateLoss() }
}
Which caused the fingerprint operations to exit with an error 5, that the operation had been cancelled.
Here is examples of how we are using the API:
fun enroll(context: Activity, username: String, refreshToken: String): Completable =
Completable.create { emitter ->
// AN-939
cleanupBiometricsFragment(context)
val callback = makeAuthenticationCallback {
try {
storeCredentials(username, refreshToken)
preferenceManager.putBoolean(PREF_BIOMETRICS_ENROLLED, true)
preferenceManager.putString(PREF_ENROLLED_EMAIL, username)
// This is what the Settings switch uses
preferenceManager.biometricsEnabled.set(true)
emitter.onComplete()
} catch (ex: Throwable) {
emitter.onError(ex)
}
}
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometrics_enroll_title))
.setDescription(context.getString(R.string.biometrics_enroll_subtitle))
.setNegativeButtonText(context.getString(R.string.biometrics_cancel))
.build()
try {
BiometricPrompt(context as FragmentActivity, MAIN_THREAD, callback)
.authenticate(promptInfo)
} catch (ex: Throwable) {
preferenceManager.putBoolean(PREF_BIOMETRICS_ENROLLED, false)
emitter.onError(ex)
}
}
Not sure, from the API if there's some way to ensure that everything is cleaned up properly at the end of the callback. I tried calling biometricPrompt.cancelAuthentication() but that also does not fix this issue.
If required, will try to make a reproduction in a small test app to demonstrate the problem.