Status Update
Comments
da...@google.com <da...@google.com> #2
For googlers: I created an internal document describe the bug, after the re-entry scenario, the ChildHelper state is inconsistent, causing a follow up findViewByPosition() returns null, and NullPointerException is thrown.
This stacks shows how the re-enter happens (line # is inaccurate since I added a lot of println in the library code)
at androidx.recyclerview.widget.RecyclerView$5.removeViewAt(RecyclerView.java:999)
at androidx.recyclerview.widget.ChildHelper.removeViewIfHidden(ChildHelper.java:393)
at androidx.recyclerview.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1620)
at androidx.recyclerview.widget.RecyclerView$ItemAnimatorRestoreListener.onAnimationFinished(RecyclerView.java:13828)
at androidx.recyclerview.widget.RecyclerView$ItemAnimator.dispatchAnimationFinished(RecyclerView.java:14330)
at androidx.recyclerview.widget.SimpleItemAnimator.dispatchMoveFinished(SimpleItemAnimator.java:301)
at androidx.recyclerview.widget.DefaultItemAnimator$6.onAnimationEnd(DefaultItemAnimator.java:316)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1112)
at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:555)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1250)
at android.animation.ValueAnimator.cancel(ValueAnimator.java:1135)
at android.view.ViewPropertyAnimator.cancel(ViewPropertyAnimator.java:425)
at androidx.leanback.widget.Presenter.cancelAnimationsRecursive(Presenter.java:191)
at androidx.leanback.widget.Presenter.onViewDetachedFromWindow(Presenter.java:183)
at androidx.leanback.widget.ItemBridgeAdapter.onViewDetachedFromWindow(ItemBridgeAdapter.java:460)
at androidx.recyclerview.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:8466)
at androidx.recyclerview.widget.RecyclerView$5.removeViewAt(RecyclerView.java:988)
at androidx.recyclerview.widget.ChildHelper.removeView(ChildHelper.java:151)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeView(RecyclerView.java:9569)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleView(RecyclerView.java:9842)
ap...@google.com <ap...@google.com> #3
Branch: androidx-main
commit 7e5641dc41bfd880b26082f0821d0db8e4962a6e
Author: Dake Gu <dake@google.com>
Date: Fri Jul 21 11:45:39 2023
leanback: add Tests for the RecyclerView ChildHelper bug
The tests are still disabled, we need fix the recyclerview and update
leanback build.gradle to point to a new version of recyclerview.
There are two tests:
1. testRandomChangeAdapterAndScroll: trying to find crashes by random
adpater update and scrolling. The test should only be run locally
2. testCrashOnRVChildHelperBug292114537 reproduced exactly the
ChildHelper bug
Bug: 157105352
Bug: 292114537
Test: remove the @Ignore tag and:
./gradlew leanback:leanback:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=androidx.leanback.widget.GridWidgetTest#testCrashOnRVChildHelperBug292114537
./gradlew leanback:leanback:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=androidx.leanback.widget.GridWidgetTest#testRandomChangeAdapterAndScroll
Change-Id: Icf8fd1373f16d5544bc14db5721a670ba7e5b7ec
M leanback/leanback/lint-baseline.xml
M leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
A leanback/leanback/src/androidTest/java/androidx/leanback/widget/TestPresenter.java
da...@google.com <da...@google.com> #4
Submitted a leanback test shows the crash steps. "setSelectedPosition(0)" removes the the item "6" that is currently sliding into the RecyclerView, it causes re-entering ChildHelper.removeViewIfHidden("6") in the middle of ChildHelper.removeView("6"). After that, ChildHelper is in a bad state for the getChildCount(), the following "setSelectedPosition(2)" throws NullPointerException.
// showing 0 1(selected) 2 3 4 5(peek)
setSelectedPosition(1);
// swap 5 and 6, showing 0 1(selected) 2 3 4 6(slide in) 5(slide out)
mActivityTestRule.runOnUiThread(() ->
adapter.setItems(TestPresenter.generateItems(new int[]{0, 1, 2, 3, 4, 6, 5, 7, 8}),
TestPresenter.DIFF_CALLBACK));
// Wait a little bit for the ItemAnimation to be started
waitForItemAnimationStart();
// Scroll to 0 to remove "6", this can break ChildHelper and scroll to 2 may failed to find
// the View and crash.
setSelectedPosition(0);
setSelectedPosition(2);
ap...@google.com <ap...@google.com> #5
Branch: androidx-main
commit 7b1d8693ce84c2e99a0972311c0abe9850315d22
Author: Dake Gu <dake@google.com>
Date: Fri Jul 21 12:02:27 2023
Fix ChildHelper removeView re-enter removeViewIfHidden
If ItemAnimation is running, a layout pass removeAndRecycleView
the animating view, the following may happen:
- Initially the ChildHelper binary bits are 1000, 4th view
is hidden to LM.
- LM tries to remove the 3rd view.
- ChildHelper.removeView modifies the binary bits (
1000 -> 100 since it's going to remove 3rd View)
- ChildHelper.removeView calls Callback.removeViewAt(2)
- Callback.removeViewAt calls dispatchViewDetached
- custom adapter dispatchViewDettached clears ViewPropertyAnimator
- ItemAnimation stops
- ChildHelper.removeViewIfHidden is called
- Everything falls apart since the ChildHelper is already
in removeView and state was changed half way. Since the
bits was changed to 100, ChildHelper incorrectly thinks
the 3rd View is hidden and calls removeViewAt(2) second
time.
It appears TV apps using Presenter are all clearing the
ViewPropertyAnimator during dispatchViewAttached due to it
wants to clear the "transient state" which prevents a view
being recycled in scroll pass. It not only stops
the custom animations, but also stops the ItemAnimation.
See the original CL that added the animate().cancel() call:
Interstingly, there was a simlar bug fix and unit test for
removing a "fade-in" view in scroll pass back in 2015:
In 2015, predictive layout wasn't supported in leanback.
Reproduce step of this bug needs predictive layout (slide
an item into the RV). So no wonder we didn't find this crash
in 2015. This bug was first reported in 2017, after predictive
animation support was added in leanback.
Since we haven't document how the app should behave in
dispatchViewDetached, even we documented, it's very likely
developer may ignore. The better fix will be making the
recyclerview more defensive.
So the CL adds a re-entering check within ChildHelper by
tracking the calling of removeView/removeViewAt/removeViewIfHidden.
Bug: 241014260
Bug: 292114537
Bug: 157105352
Test: Created recyclerview tests based on the fake LayoutManager:
removeSlideInViewLeftToSlideOutViewAndCancelAnimationInOnDetach
removeSlideInViewLeftToSlideOutView
Test: A leanback regression test running on the real layoutmanager
was already merged (but disabled pending on the fix)
Change-Id: I42f22b0e805db12ee4cd478b3beb6b9d1a86cdaa
M recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewAnimationsTest.java
M recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/BaseRecyclerViewInstrumentationTest.java
M recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAnimationsTest.java
M recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ChildHelper.java
da...@google.com <da...@google.com> #6
ry...@google.com <ry...@google.com> #7
Will be released with next alpha release, barring anything unexpected.
pr...@google.com <pr...@google.com> #8
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.leanback:leanback:1.2.0-alpha03
da...@google.com <da...@google.com> #9
No, the fix is in recyclerview, not in leanback. Recyclerview hasn't released a alpha or beta yet with this fix.
da...@google.com <da...@google.com> #10
pr...@google.com <pr...@google.com> #11
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.recyclerview:recyclerview:1.4.0-alpha01
androidx.recyclerview:recyclerview:1.3.2
da...@google.com <da...@google.com>
ap...@google.com <ap...@google.com> #12
Branch: androidx-main
commit f87647a9de4c5b4308b2a6ebe299441f74f8d740
Author: Dake Gu <dake@google.com>
Date: Fri Oct 27 10:18:25 2023
leanback: update recyclerview requirement to 1.3.2
This is to include the important recyclerview fix that is
common in leanback apps.
framework fragment Tests are regenerated.
Bug: 292114537
Test: ./gradlew leanback:leanback:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=androidx.leanback.widget.GridWidgetTest#testCrashOnRVChildHelperBug292114537
Change-Id: I2c3a0f7ae43f72bd6a1dbbe30c269148f824a885
M leanback/leanback-grid/build.gradle
M leanback/leanback-grid/src/main/java/androidx/leanback/widget/GridLayoutManager.java
M leanback/leanback-preference/build.gradle
M leanback/leanback/build.gradle
M leanback/leanback/src/androidTest/generatev4.py
M leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseSupportFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/DetailsFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/DetailsTestFragment.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/GuidedStepFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/GuidedStepTestFragment.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/RowsFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/RowsSupportFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchSupportFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/VerticalGridFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/app/VerticalGridSupportFragmentTest.java
M leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
Description