2 min read

Fixing a crash in ViewGroup offsetRectBetweenParentAndChild

Description

I was a tracking an originally low volume bug for about 6 months that gradually worked its way into our top crashes. The stacktrace had the clue all along but I kept missing what it was pointing out.

android.view.ViewGroup.offsetRectBetweenParentAndChild (ViewGroup.java:6296)
android.view.ViewGroup.offsetDescendantRectToMyCoords (ViewGroup.java:6225)
com.android.internal.view.RecyclerViewCaptureHelper.onScrollRequested (RecyclerViewCaptureHelper.java:114)
com.android.internal.view.RecyclerViewCaptureHelper.onScrollRequested (RecyclerViewCaptureHelper.java:42)
com.android.internal.view.ScrollCaptureViewSupport.onScrollCaptureImageRequest (ScrollCaptureViewSupport.java:253)
android.view.ScrollCaptureConnection.lambda$requestImage$1$android-view-ScrollCaptureConnection (ScrollCaptureConnection.java:149)
android.view.ScrollCaptureConnection$$ExternalSyntheticLambda4.run (Unknown Source:6)
android.os.Handler.handleCallback (Handler.java:942)

The first place I started looking was around ViewGroup.offsetRectBetweenParentAndChild, which used to have issues many years ago in early Android days. Along the way I found a reference to [a race condition between physical trackball and IME keyboard], a blog post by a Chinese developer who uncovered a bug in ViewGroup's source code. This behavior was fixed in Android 16 (and still has comments in AndroidX RecyclerView source today). Because these issues were so old but seemingly had established fixes I was confused as to how the behavior referenced could be happening today, and specifically only on Android 13.

I finally was about to throw in the towel and put on a blanket fix using android:descendantFocusability="beforeDescendants", but then I looked at the stack trace one more time. I read each line and classname and looked up each one until I got to ScrollCaptureViewSupport, whose source I've never read before, but I immediately connected it with why we would only see this crash on Android 13 - because it is a feature introduced in Android 13 to capture scrolling screenshots.

I made a simple reproduction app and had success reproducing the crash with these steps:

  1. The list must have a focusable element that the user interacts with for it to gain focus.
  2. That view gains focus
  3. The user scrolls away from the focused view
  4. The user opens the soft keyboard (in Chat this means tapping on message composition EditText
  5. The user takes a screenshot
  6. The user chooses "Capture More"

Now that I had clear STR, I attempted a fix that was mentioned in a a very old question post. Funny enough the answer is from October 2022, and it makes sense - get the window's currently focused item and clear it before attempting to show the keyboard after Android 13 was released.

Therefore I'm reworking our keyboard extensions to ensure we do clear focus whenever requesting to show the keyboard. We cleaned up some deprecated code along the way and removed android:focusable="true" where it wasn't needed.