From da2bd14d0f8898f51c1d73a0d4221380a3b6400c Mon Sep 17 00:00:00 2001 From: Mehran latifi Date: Mon, 27 Jul 2026 16:24:54 +0330 Subject: [PATCH] Fix crash when a list updates while a screen reader reads an item A content changed event dispatched at the end of a layout pass makes the framework build the node info of the focused item right away, which asks the layout manager for item positions that the finished layout has not settled yet and throws IndexOutOfBoundsException. It shows up when messages load underneath a screen reader, for example after opening a message from search or scrolling comments. Ignore the layout manager while positions are still pre layout, the same way pending adapter updates are already ignored, and treat a position that no longer exists as unknown instead of throwing. --- .../androidx/recyclerview/widget/GridLayoutManager.java | 6 ++++++ .../widget/RecyclerViewAccessibilityDelegate.java | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/TMessagesProj/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java b/TMessagesProj/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java index 5f50bb49c93..96e4a03830c 100644 --- a/TMessagesProj/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java +++ b/TMessagesProj/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java @@ -443,6 +443,12 @@ private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State if (!state.isPreLayout()) { return mSpanSizeLookup.getCachedSpanGroupIndex(viewPosition, mSpanCount); } + if (viewPosition < 0 || viewPosition >= state.getItemCount()) { + // the position belongs to a layout that is already gone, as happens when the item is + // asked about from a content changed event dispatched while the list is being laid out + Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition); + return 0; + } final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition); if (adapterPosition == -1) { if (DEBUG) { diff --git a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java index adfbb436955..b4058035128 100644 --- a/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java +++ b/TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java @@ -40,7 +40,13 @@ public RecyclerViewAccessibilityDelegate(@NonNull RecyclerView recyclerView) { } boolean shouldIgnore() { - return mRecyclerView.hasPendingAdapterUpdates(); + // item positions are only settled once a layout pass is over: asking the layout manager + // about them from inside one, as a content changed event dispatched from layout does, + // would read positions that no longer exist and throw. the pre layout check is needed on + // its own because that event is dispatched right after the layout counter is cleared + return mRecyclerView.hasPendingAdapterUpdates() + || mRecyclerView.isComputingLayout() + || mRecyclerView.mState.isPreLayout(); } @Override