bitfireAT/cert4android

Exception java.lang.IllegalStateException: Already resumed, but proposed with update false

Closed this issue · 8 comments

I've copied this from the Google Play Console. Not occurring often but we should fix it ;-)

Exception java.lang.IllegalStateException: Already resumed, but proposed with update false
  at kotlinx.coroutines.CancellableContinuationImpl.alreadyResumedError (CancellableContinuationImpl.kt:559)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:524)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:497)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith (CancellableContinuationImpl.kt:368)
  at at.bitfire.cert4android.UserDecisionRegistry.onUserDecision (UserDecisionRegistry.kt:113)
  at at.bitfire.cert4android.TrustCertificateActivity$Model.registerDecision (TrustCertificateActivity.kt:312)
  at at.bitfire.cert4android.TrustCertificateActivity$onCreate$2.handleOnBackPressed (TrustCertificateActivity.kt:77)
  at androidx.activity.OnBackPressedDispatcher.onBackPressed (OnBackPressedDispatcher.kt:213)
  at androidx.activity.ComponentActivity.onBackPressed (ComponentActivity.java:694)
  at android.app.Activity.onKeyUp (Activity.java:3940)
  at android.view.KeyEvent.dispatch (KeyEvent.java:3143)
  at android.app.Activity.dispatchKeyEvent (Activity.java:4286)
  at androidx.core.app.ComponentActivity.superDispatchKeyEvent (ComponentActivity.java:126)
  at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent (KeyEventDispatcher.java:86)
  at androidx.core.app.ComponentActivity.dispatchKeyEvent (ComponentActivity.java:144)
  at com.android.internal.policy.DecorView.dispatchKeyEvent (DecorView.java:415)
  at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent (ViewRootImpl.java:6664)
  at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess (ViewRootImpl.java:6530)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:5990)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:6047)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:6013)
  at android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:6178)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:6021)
  at android.view.ViewRootImpl$AsyncInputStage.apply (ViewRootImpl.java:6235)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:5994)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:6047)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:6013)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:6021)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:5994)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:6047)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:6013)
  at android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:6211)
  at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent (ViewRootImpl.java:6391)
  at android.view.inputmethod.InputMethodManager$PendingEvent.run (InputMethodManager.java:3667)
  at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback (InputMethodManager.java:3187)
  at android.view.inputmethod.InputMethodManager.finishedInputEvent (InputMethodManager.java:3178)
  at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished (InputMethodManager.java:3644)
  at android.view.InputEventSender.dispatchInputEventFinished (InputEventSender.java:154)
  at android.os.MessageQueue.nativePollOnce
  at android.os.MessageQueue.next (MessageQueue.java:335)
  at android.os.Looper.loopOnce (Looper.java:185)
  at android.os.Looper.loop (Looper.java:359)
  at android.app.ActivityThread.main (ActivityThread.java:8127)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:942)

I'm trying everything that comes to my mind to reproduce this, with no success. It's clear from the error that it's thrown by pressing back after a certificate decision, which sends a second result, causing the error.

I've tried tapping reject and back as quick as possible, and it's not feasible that this is the issue. The other thing that I can think of is that the activity is not getting closed for some reason after accepting the certificate, and the user tries to tap back, which gives a second answer. However, I'm not able to force this behavior by any means.

Update: Not only happens on back press but also on tapping:

Exception java.lang.IllegalStateException: Already resumed, but proposed with update true
  at kotlinx.coroutines.CancellableContinuationImpl.alreadyResumedError (CancellableContinuationImpl.kt:559)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:524)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:497)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith (CancellableContinuationImpl.kt:368)
  at at.bitfire.cert4android.UserDecisionRegistry.onUserDecision (UserDecisionRegistry.kt:113)
  at at.bitfire.cert4android.TrustCertificateActivity$Model.registerDecision (TrustCertificateActivity.kt:312)
  at at.bitfire.cert4android.TrustCertificateActivity$CertificateCard$1$1$2$1.invoke (TrustCertificateActivity.kt:211)
  at at.bitfire.cert4android.TrustCertificateActivity$CertificateCard$1$1$2$1.invoke (TrustCertificateActivity.kt:208)
  at androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke-k-4lQ0M (Clickable.kt:895)
  at androidx.compose.foundation.ClickablePointerInputNode$pointerInput$3.invoke (Clickable.kt:889)
  at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend (TapGestureDetector.kt:255)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:179)
  at kotlinx.coroutines.CancellableContinuationImpl.takeState$kotlinx_coroutines_core (CancellableContinuationImpl.kt:168)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:168)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:474)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:508)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:497)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith (CancellableContinuationImpl.kt:368)
  at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl$PointerEventHandlerCoroutine.offerPointerEvent (SuspendingPointerInputModifierNodeImpl.java:665)
  at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl.dispatchPointerEvent (SuspendingPointerInputFilter.kt:544)
  at androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNodeImpl.onPointerEvent-H0pRuoY (SuspendingPointerInputFilter.kt:566)
  at androidx.compose.foundation.AbstractClickablePointerInputNode.onPointerEvent-H0pRuoY (AbstractClickablePointerInputNode.java:855)
  at androidx.compose.foundation.AbstractClickableNode.onPointerEvent-H0pRuoY (Clickable.kt:703)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:317)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:303)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:303)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:303)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:303)
  at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass (HitPathTracker.kt:303)
  at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass (NodeParent.java:183)
  at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges (HitPathTracker.kt:102)
  at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog (PointerInputEventProcessor.kt:96)
  at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc (AndroidComposeView.android.kt:1446)
  at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc (AndroidComposeView.android.kt:1398)
  at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent (AndroidComposeView.android.kt:1338)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3361)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3035)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3361)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3035)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3361)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3035)
  at android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3361)
  at android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:3035)
  at com.android.internal.policy.DecorView.superDispatchTouchEvent (DecorView.java:1127)
  at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent (PhoneWindow.java:1971)
  at android.app.Activity.dispatchTouchEvent (Activity.java:4410)
  at com.android.internal.policy.DecorView.dispatchTouchEvent (DecorView.java:1079)
  at android.view.View.dispatchPointerEvent (View.java:15866)
  at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent (ViewRootImpl.java:8264)
  at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess (ViewRootImpl.java:7955)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:7291)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:7348)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:7314)
  at android.view.ViewRootImpl$AsyncInputStage.forward (ViewRootImpl.java:7512)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:7322)
  at android.view.ViewRootImpl$AsyncInputStage.apply (ViewRootImpl.java:7569)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:7295)
  at android.view.ViewRootImpl$InputStage.onDeliverToNext (ViewRootImpl.java:7348)
  at android.view.ViewRootImpl$InputStage.forward (ViewRootImpl.java:7314)
  at android.view.ViewRootImpl$InputStage.apply (ViewRootImpl.java:7322)
  at android.view.ViewRootImpl$InputStage.deliver (ViewRootImpl.java:7295)
  at android.view.ViewRootImpl.deliverInputEvent (ViewRootImpl.java:10971)
  at android.view.ViewRootImpl.doProcessInputEvents (ViewRootImpl.java:10853)
  at android.view.ViewRootImpl.enqueueInputEvent (ViewRootImpl.java:10809)
  at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent (ViewRootImpl.java:11109)
  at android.view.InputEventReceiver.dispatchInputEvent (InputEventReceiver.java:295)
  at android.os.MessageQueue.nativePollOnce
  at android.os.MessageQueue.next (MessageQueue.java:335)
  at android.os.Looper.loopOnce (Looper.java:186)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8810)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:604)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

I've only found it crashing on Android 13, so maybe something to take into consideration...

Maybe replacing the iterator call

// continue work that's waiting for decisions
pendingDecisions[cert]?.iterator()?.let { iter ->
while (iter.hasNext()) {
iter.next().resume(trusted)
iter.remove()
}
}

with something like?

synchronized(pendingDecisions) {
    pendingDecisions[cert]?.removeAll {
        it.resume(trusted)
        true
    }
}

Or directly annontating onUserDecision with @Synchronized?

Tried things:

  • Quickly tapping multiple options at the same time (eg back and reject, or reject and accept)
  • Dismiss notification with decision activity open (activity just closes automatically)

I think this might be related to timeout. In fact, I think it doesn't even work at all. My thoughts are that timeout is giving an answer to the request, but not closing the activity, so whenever you press back, deny or whatever, you are giving an extra answer, which causes the error. I haven't been able to reproduce it yet, though.

Also no clue why it's only happening on Android 13.

Update: this is not the issue, but it's a new thing to fix. When the request times out, the Activity doesn't close. Something to look into.

Update 2: Timeout is not inside the suspendCancellableCoroutine, not even inside the UserDecisionRegistry, so it has nothing to do with the continuation. So not related to the issue, but something to take into account.

I think this might be related to timeout. In fact, I think it doesn't even work at all. My thoughts are that timeout is giving an answer to the request, but not closing the activity, so whenever you press back, deny or whatever, you are giving an extra answer, which causes the error. I haven't been able to reproduce it yet, though.

That's by intention (see #30), however accepting/denying shouldn't give an extra answer. I didn't see where this could happen by now…

Also no clue why it's only happening on Android 13.

This code is only executing in Android 13 (because the notification permission only exists since Android 13) and it contains a cont.resume()… quite suspicious:

} else if (!notificationShown) {
Cert4Android.log.warning("App not in foreground and missing notification permission, rejecting certificate")
// return false to caller of suspending function
cont.resume(false)
}

Found out how to reproduce it 😃

  1. Open cert4android demo in Android 13. Make sure that notification permissions are not granted.
  2. Uncheck App in foreground
  3. Access URL with self-signed certificate. The code cited above sends cont.resume(false) and the request fails. However, the request is still in pendingDecisions.
  4. Check App in foreground.
  5. Access URL with self-signed certificate again. The activity opens. Accept the certificate.
  6. The app crashes with java.lang.IllegalStateException: Already resumed, but proposed with update false because all pendingDecisions are continued (including the one that has already been continued because the app was in background).