Status Update
Comments
bl...@google.com <bl...@google.com> #2
Hi Sunkesula, which coroutine scope did you use?
re...@google.com <re...@google.com>
ar...@google.com <ar...@google.com> #3
If you use a lifecycle scope then the operation is expected to automatically cancel upon the destroy of the lifecycle owner (e.g. activity lifecycle scope cancels upon activity destroyed by configuration changes).
You need a different scope (e.g. view model scope) to survive across activity lifecycles. Meanwhile, the CredentialManager api takes in an activity: please do not cache that activity into your view model to use view model scope as that can cause memory leak when the activity gets destroyed. Instead, you would want to dynamically get your activity from a callback at the time of the invocation.
dm...@ownid.com <dm...@ownid.com> #4
Well, it doesn't help. Even worse. I changed scope to viewModelScope that is created within activity and problem is still there - all the dobbling stuff. If I pass biometry then it crashes with : java.lang.IllegalStateException: Already resumed, but proposed with update CompletedExceptionally[androidx.credentials.exceptions.GetCredentialCancellationException: androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController activity is cancelled by the user.] at kotlinx.coroutines.CancellableContinuationImpl.alreadyResumedError(CancellableContinuationImpl.kt:482)
ar...@google.com <ar...@google.com> #5
Could you share the code snippet of the scope creation and the call invocation?
ar...@google.com <ar...@google.com> #6
class LoginFragment : Fragment() {
private val loginViewModel: LoginViewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(this).get(LoginViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<Button>(R.id.button).setOnClickListener {
loginViewModel.doLogin(requireActivity())
}
}
}
class LoginViewModel : ViewModel() {
fun doLogin(activity: Activity) {
val requestJson = """{"challenge":"SDRfcnFXM2hhMGFvdXNRcFJKcXFZQQ","timeout":120000,"rpId":"dev.ownid.com","userVerification":"required","allowCredentials":["q6g-h9toC9NtisDkWXfAFg"]}"""
viewModelScope.launch {
try {
val getRequest = GetCredentialRequest(listOf(GetPublicKeyCredentialOption(requestJson)))
val result = CredentialManager.create(activity).getCredential(getRequest, activity)
handleSignIn(result)
} catch (e: GetCredentialException) {
handleFailure(e)
}
}
}
}
On button click getting passkey selection dialog (see first png).
Next rotate device and you get two of them - one on top another (see second png).
Complete the top passkey request (no result from getCredential())
Close the bottom dialog (getting successful result from getCredential()).
Instantly app crashes as we get error on main thread:
java.lang.IllegalStateException: Already resumed, but proposed with update CompletedExceptionally[androidx.credentials.exceptions.GetCredentialCancellationException: androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController activity is cancelled by the user.]
at kotlinx.coroutines.CancellableContinuationImpl.alreadyResumedError(CancellableContinuationImpl.kt:482)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:447)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at androidx.credentials.CredentialManager$getCredential$2$callback$1.onError(CredentialManager.kt:121)
at androidx.credentials.CredentialManager$getCredential$2$callback$1.onError(CredentialManager.kt:114)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$2.invoke$lambda$0(CredentialProviderBeginSignInController.kt:140)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$2.$r8$lambda$WczJB-r-nGpR5_auZ66rHEEZm7Y(Unknown Source:0)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$2$$ExternalSyntheticLambda0.run(Unknown Source:4)
at androidx.credentials.CredentialManager$$ExternalSyntheticLambda0.execute(Unknown Source:0)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$2.invoke(CredentialProviderBeginSignInController.kt:139)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$2.invoke(CredentialProviderBeginSignInController.kt:137)
at androidx.credentials.playservices.controllers.CredentialProviderController$Companion$maybeReportErrorResultCodeGet$1.invoke(CredentialProviderController.kt:116)
at androidx.credentials.playservices.controllers.CredentialProviderController$Companion$maybeReportErrorResultCodeGet$1.invoke(CredentialProviderController.kt:116)
at androidx.credentials.playservices.controllers.CredentialProviderController$Companion.cancelOrCallbackExceptionOrResult(CredentialProviderController.kt:134)
at androidx.credentials.playservices.controllers.CredentialProviderController.cancelOrCallbackExceptionOrResult(Unknown Source:2)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.access$cancelOrCallbackExceptionOrResult$s895630660(CredentialProviderBeginSignInController.kt:57)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$1.invoke(CredentialProviderBeginSignInController.kt:138)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$handleResponse$1.invoke(CredentialProviderBeginSignInController.kt:137)
at androidx.credentials.playservices.controllers.CredentialProviderController$Companion.maybeReportErrorResultCodeGet(CredentialProviderController.kt:116)
at androidx.credentials.playservices.controllers.CredentialProviderController.maybeReportErrorResultCodeGet(Unknown Source:7)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.handleResponse$credentials_play_services_auth_release(CredentialProviderBeginSignInController.kt:137)
at androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$resultReceiver$1.onReceiveResult(CredentialProviderBeginSignInController.kt:100)
at android.os.ResultReceiver$MyRunnable.run(ResultReceiver.java:50)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7884)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
dm...@ownid.com <dm...@ownid.com> #7
Maybe it helps. LeakCanary indicates leak after screen rotation:
┬───
│ GC Root: Global variable in native code
│
├─ android.os.ResultReceiver$MyResultReceiver instance
│ Leaking: UNKNOWN
│ Retaining 274.0 kB in 5254 objects
│ ↓ ResultReceiver$MyResultReceiver.this$0
│ ~~~~~~
├─ androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController$resultReceiver$1
│ instance
│ Leaking: UNKNOWN
│ Retaining 273.5 kB in 5253 objects
│ Anonymous subclass of android.os.ResultReceiver
│ ↓ CredentialProviderBeginSignInController$resultReceiver$1.this$0
│ ~~~~~~
├─ androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController instance
│ Leaking: UNKNOWN
│ Retaining 273.5 kB in 5251 objects
│ activity instance of com.ownid.demo.firebase.ui.activity.MainActivity with mDestroyed = true
│ ↓ CredentialProviderBeginSignInController.activity
│ ~~~~~~~~
╰→ com.ownid.demo.firebase.ui.activity.MainActivity instance
​ Leaking: YES (ObjectWatcher was watching this because com.ownid.demo.firebase.ui.activity.MainActivity received
​ Activity#onDestroy() callback and Activity#mDestroyed is true)
​ Retaining 273.1 kB in 5238 objects
​ key = 1c3d91c7-c738-4bc5-8dba-95247ac05760
​ watchDurationMillis = 41713
​ retainedDurationMillis = 36712
​ mApplication instance of com.ownid.demo.firebase.DemoApp
​ mBase instance of androidx.appcompat.view.ContextThemeWrapper
====================================
ar...@google.com <ar...@google.com> #8
Thank you for your help @dimitriy@ownid.com. You are right in that we don’t properly handle the configuration changes. Thank you for identifying this bug. What our upcoming changes can do is consistently ensure that upon rotation, our system stays consistent. I think the expected behavior here should depend upon the app - so possibly but not necessarily always will there be a cancellation. At the moment, the 'cancellation' will look strange, but is effectively doing a cancellation.
However, the application that our system runs on must also account for a configuration change. This can be done by matching the configuration consistency shown
<activity
android:name=".ExampleMainActivity"
android:configChanges="keyboardHidden|orientation|screenSize" // This line
......>
If the underlying application does not account for the configuration change, then the expected response will never make it to the designated receiver, because the underlying application is no longer in the same state as our system. Thus, the bottom sheet will remain up, but the credentials will land no-where when clicked or closed. It is, effectively, ‘canceled’, but it will look strange.
This may be worth following up so that both cases are handled (ensuring those who have set the most widely used configuration changes still get a consistent experience and ensure those who do not get a complete cancellation rather than the strange 'UI-still-up' version that exists today). However, for now, the former is the targeted solution and hopefully it can help avoid the strange behavior you experienced once the next version is released.
dm...@ownid.com <dm...@ownid.com> #9
The videos below showcase the now expected behaviour. The 'AppConfigured' assumes the underlying app is properly configured to handle these core changes. The 'NonConfiguredDefaultCanceled' shows the 'UI-still-up' version of cancellation. In all cases, the repeating bottom-sheets have been fixed, and in the case when the underlying app is properly configured, the final credential is properly returned to the application as expected.
ar...@google.com <ar...@google.com> #10
Branch: androidx-main
commit f34fe3a42b515adafe4d50841df9ae6a1b836c06
Author: Arpan Kaphle <akaphle@google.com>
Date: Wed Apr 05 21:36:35 2023
Fixing Hidden Activity Configuration Change Issue
From external users, there was un-intentional and un-intended behaviour
of the play auth module during configuration changes, namely rotation.
This provides a fix, which was tested. However, this fix only ensures no
duplicate bottom sheets from play auth. Users who utilize this must also
make the corresponding change to their applications. Otherwise, the
application state will re-start their activity, and will cause the play
auth bottom sheet to be effectively 'canceled', but still appear 'up'.
This behaviour could be further examined and a plan setup to avoid the
strange looking canceled.
Bug: 276939495
Bug: 276316128
Test: Tested on Device and Built
Change-Id: Ie8a6c0db52b98766274c15dc424f4485f79128d3
M credentials/credentials-play-services-auth/src/main/AndroidManifest.xml
ar...@google.com <ar...@google.com>
sg...@google.com <sg...@google.com>
sg...@google.com <sg...@google.com> #11
Thanks for reply. I see the proposed changes as a temporal workaround and not as proper library behavior on configuration changes. Even more - the proposed changes "fix" only some cases for configuration changes, not all of them.
In addition to that, proposed fix requires changes to be introduced to the customers application, in particular how activity handles configurations changes. This may not be acceptable way to fix the issue, especially in my case as SDK developer. I cannot ask customer to do such changes as it may break many things in there production app.
Bottom line - proposed solution does not solve the problem.
ap...@google.com <ap...@google.com> #12
na...@google.com <na...@google.com> #14
Ok
Description
Version used: androidx.credentials:credentials-play-services-auth:1.0.0-alpha09
Looks like there is no R8/Proguard rules embedded in the lib.
There was a commit specifically for that:
but I cannot find a `proguard.txt` file in `credentials-play-services-auth-1.0.0-alpha09.aar`
I know that there is an instruction to add rules manually
but why we have to do this manually?