Status Update
Comments
su...@google.com <su...@google.com>
he...@google.com <he...@google.com> #2
Hi Sunkesula, which coroutine scope did you use?
he...@google.com <he...@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)
he...@google.com <he...@google.com> #5
Could you share the code snippet of the scope creation and the call invocation?
dm...@ownid.com <dm...@ownid.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
====================================
he...@google.com <he...@google.com>
ak...@google.com <ak...@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.
ak...@google.com <ak...@google.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.
ap...@google.com <ap...@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
dm...@ownid.com <dm...@ownid.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.
ah...@gmail.com <ah...@gmail.com> #12
ah...@gmail.com <ah...@gmail.com> #13
ah...@gmail.com <ah...@gmail.com> #14
Ok
ah...@gmail.com <ah...@gmail.com> #15
F
ah...@gmail.com <ah...@gmail.com> #16
ah...@gmail.com <ah...@gmail.com> #17 Restricted+
ap...@google.com <ap...@google.com> #18
Branch: androidx-main
commit a75fcac0d319a411a6d750119b9ac2e0f3585a7c
Author: Arpan Kaphle <akaphle@google.com>
Date: Mon Apr 10 20:56:24 2023
Configuration Change Consistency in Play Auth Flow
This change is a follow up to a prior change that handles more general
configuration changes - or really anything that restarts the app
(assuming it is run in the proper scope, namely a non cancellation
inducing scope in the programming). This ensures that the behaviour of the underlying
module is consistent and returns credentials as expected.
Bug: 276316128
Bug: 276939495
Test: E2E Test and Build
Change-Id: Iae8ce99fa56d237d874c5af2c61a914aaaeb85e4
M credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
M credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
M credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
ak...@google.com <ak...@google.com> #19
I hope you can excuse me for the delayed response. We have put up a new solution that should contain the expected behavior. If the calling application uses a cancellable scope as @helenqin@google.com mentioned above, a proper JobCancellationException will be thrown. If a non cancellable scope is used, even on configuration change, our library will ensure proper return of credentials. You can expect this change to arrive in the next release, around May.
In the meantime, to try this out you can use a snapshot SDK from
Step 1: Add the following maven url line to your build.gradle file.
allprojects {
repositories {
google()
jcenter()
maven { url 'https://androidx.dev/snapshots/builds/9928302/artifacts/repository' }
}
}
Step 2: Depend on the snapshot version artifact:
dependencies {
def work_version = '1.0.0-SNAPSHOT'
implementation "androidx.credentials:credentials-play-services-auth:$work_version"
...
}
Thank you for your patience, and please let us know if there are any other issues. We appreciate your help in making Credential Manager better prior to the final launch.
sg...@google.com <sg...@google.com>
dm...@ownid.com <dm...@ownid.com> #20
I confirm that issue is fixed. Thanks! However memory leak is still present. Will open another issue for that.
pr...@google.com <pr...@google.com> #21
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.credentials:credentials-play-services-auth:1.0.0-alpha07
gu...@gmail.com <gu...@gmail.com> #22
When I call getCredential() and the identity selection pops up, I select an account and the popover cancels, and then nothing
The code:
fun Login(mActivity:Activity,mCredentialManager: CredentialManager,mGoogleIdOption: GetGoogleIdOption){
viewModelScope.launch {
try {
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(mGoogleIdOption)
.build()
val result = mCredentialManager.getCredential(mActivity,request)
if (result != null) {
handleSignIn(result)
} else {
}
} catch (e: GetCredentialException) {
Log.e(LoginActivity.TAG, e.toJson())
}
}
}
Error: {"errorMessage":"activity is cancelled by the user.","type":"android.credentials.GetCredentialException. TYPE_USER_CANCELED","detailMessage":"activity is cancelled by the user.","stackTrace":[]}
Configured version: implementation "com.google.android.libraries.identity.googleid:googleid:1.1.0"
implementation("androidx.credentials:credentials:1.3.0-alpha01")
implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha01”)
gu...@gmail.com <gu...@gmail.com> #23
sg...@google.com <sg...@google.com> #24
This looks like a different issue, could you please open a new bug with more details about which version of jetpack you're using, which version of android you're running, a bug report and etc.
Description
Component used: Credentials
Version used: androidx.credentials:credentials:1.0.0-alpha05
androidx.credentials:credentials-play-services-auth:1.0.0-alpha05
Steps to reproduce:
On Passkeys prompt slider (or on biometry/pin prompt) rotate device (trigger configuration change)
Result: coroutineScope is canceled but credential operation is not, causing:
Expected correct behavior: Passkey operation is canceled correctly: