diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-05-17 06:06:27 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-05-17 06:06:27 +0000 |
commit | 346440fe4046777c435e51ab7c1f9719d024bc9b (patch) | |
tree | 70d659498f446356fd8e68cdec6355dfc2be11d2 | |
parent | 37f2f2effd10f4fdd34273160a2ef3fafecd3442 (diff) | |
parent | d52bd339fd007893bf109ce02fe55aeb53d0ade7 (diff) | |
download | support-snap-temp-L86300000960732273.tar.gz |
Merge "Merge cherrypicks of ['android-review.googlesource.com/2414514', 'android-review.googlesource.com/2366713', 'android-review.googlesource.com/2414515', 'android-review.googlesource.com/2485975', 'android-review.googlesource.com/2487270', 'android-review.googlesource.com/2485841', 'android-review.googlesource.com/2494715', 'android-review.googlesource.com/2478357', 'android-review.googlesource.com/2501817', 'android-review.googlesource.com/2478185', 'android-review.googlesource.com/2564691', 'android-review.googlesource.com/2593587'] into androidx-recyclerview-release." into androidx-recyclerview-releasesnap-temp-L86300000960732273snap-temp-L72700000960727734snap-temp-L65500000960727253snap-temp-L59300000960730134snap-temp-L18600000960728297
20 files changed, 267 insertions, 96 deletions
diff --git a/libraryversions.toml b/libraryversions.toml index 428ece9fc93..fb8ec465e44 100644 --- a/libraryversions.toml +++ b/libraryversions.toml @@ -88,7 +88,7 @@ PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha01" PRIVACYSANDBOX_TOOLS = "1.0.0-alpha01" PROFILEINSTALLER = "1.3.0-alpha01" RECOMMENDATION = "1.1.0-alpha01" -RECYCLERVIEW = "1.3.0" +RECYCLERVIEW = "1.3.1-rc01" RECYCLERVIEW_SELECTION = "1.2.0-alpha02" REMOTECALLBACK = "1.0.0-alpha02" RESOURCEINSPECTION = "1.1.0-alpha01" diff --git a/recyclerview/recyclerview/api/current.txt b/recyclerview/recyclerview/api/current.txt index 7e45fbbe082..7f72d0ed83d 100644 --- a/recyclerview/recyclerview/api/current.txt +++ b/recyclerview/recyclerview/api/current.txt @@ -470,6 +470,7 @@ package androidx.recyclerview.widget { method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?); method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?); method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?); + method public static void setDebugAssertionsEnabled(boolean); method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory); method public void setHasFixedSize(boolean); method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?); @@ -483,6 +484,7 @@ package androidx.recyclerview.widget { method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?); method @Deprecated public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?); method public void setScrollingTouchSlop(int); + method public static void setVerboseLoggingEnabled(boolean); method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?); method public void smoothScrollBy(@Px int, @Px int); method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?); diff --git a/recyclerview/recyclerview/api/public_plus_experimental_current.txt b/recyclerview/recyclerview/api/public_plus_experimental_current.txt index 7e45fbbe082..7f72d0ed83d 100644 --- a/recyclerview/recyclerview/api/public_plus_experimental_current.txt +++ b/recyclerview/recyclerview/api/public_plus_experimental_current.txt @@ -470,6 +470,7 @@ package androidx.recyclerview.widget { method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?); method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?); method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?); + method public static void setDebugAssertionsEnabled(boolean); method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory); method public void setHasFixedSize(boolean); method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?); @@ -483,6 +484,7 @@ package androidx.recyclerview.widget { method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?); method @Deprecated public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?); method public void setScrollingTouchSlop(int); + method public static void setVerboseLoggingEnabled(boolean); method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?); method public void smoothScrollBy(@Px int, @Px int); method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?); diff --git a/recyclerview/recyclerview/api/restricted_current.txt b/recyclerview/recyclerview/api/restricted_current.txt index ac7b64b451b..dcec5a169fc 100644 --- a/recyclerview/recyclerview/api/restricted_current.txt +++ b/recyclerview/recyclerview/api/restricted_current.txt @@ -470,6 +470,7 @@ package androidx.recyclerview.widget { method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?); method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?); method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?); + method public static void setDebugAssertionsEnabled(boolean); method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory); method public void setHasFixedSize(boolean); method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?); @@ -483,6 +484,7 @@ package androidx.recyclerview.widget { method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?); method @Deprecated public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?); method public void setScrollingTouchSlop(int); + method public static void setVerboseLoggingEnabled(boolean); method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?); method public void smoothScrollBy(@Px int, @Px int); method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?); diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle index d1a017d2e0e..004c1f775ee 100644 --- a/recyclerview/recyclerview/build.gradle +++ b/recyclerview/recyclerview/build.gradle @@ -34,6 +34,9 @@ dependencies { testImplementation(libs.mockitoCore) testImplementation(libs.kotlinStdlib) lintPublish(project(":recyclerview:recyclerview-lint")) + constraints { + implementation(project(":viewpager2:viewpager2")) + } } android { @@ -47,7 +50,7 @@ android { defaultConfig { multiDexEnabled = true - testInstrumentationRunner "androidx.testutils.ActivityRecyclingAndroidJUnitRunner" + testInstrumentationRunner "androidx.recyclerview.test.TestRunner" multiDexEnabled true } namespace "androidx.recyclerview" diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt new file mode 100644 index 00000000000..05f598a0d4f --- /dev/null +++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.test + +import android.os.Bundle +import androidx.recyclerview.widget.RecyclerView +import androidx.testutils.ActivityRecyclingAndroidJUnitRunner + +class TestRunner : ActivityRecyclingAndroidJUnitRunner() { + override fun onCreate(arguments: Bundle?) { + super.onCreate(arguments) + RecyclerView.setDebugAssertionsEnabled(true) + } + + override fun onDestroy() { + RecyclerView.setDebugAssertionsEnabled(false) + super.onDestroy() + } +}
\ No newline at end of file diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java index a0839484e41..4ba2c5981e6 100644 --- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java +++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java @@ -825,6 +825,19 @@ public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) @Test public void hiddenNoneRemoveViewAccessibility() throws Throwable { + // TODO(b/263592347): remove the RecyclerView.setDebugAssertionsEnabled calls + // and combine this into the impl method + // This is just a separate method to temporarily wrap the whole thing in a try/finally + // block without messing with git history too much. + RecyclerView.setDebugAssertionsEnabled(false); + try { + hiddenNoneRemoveViewAccessibilityImpl(); + } finally { + RecyclerView.setDebugAssertionsEnabled(true); + } + } + + public void hiddenNoneRemoveViewAccessibilityImpl() throws Throwable { final Config config = new Config(); int adapterSize = 1000; final boolean[] firstItemSpecialSize = new boolean[] {false}; diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GapWorker.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GapWorker.java index b2d9f2e7781..5bbdcf1b489 100644 --- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GapWorker.java +++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GapWorker.java @@ -157,7 +157,7 @@ final class GapWorker implements Runnable { } public void add(RecyclerView recyclerView) { - if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) { + if (RecyclerView.sDebugAssertionsEnabled && mRecyclerViews.contains(recyclerView)) { throw new IllegalStateException("RecyclerView already present in worker list!"); } mRecyclerViews.add(recyclerView); @@ -165,7 +165,7 @@ final class GapWorker implements Runnable { public void remove(RecyclerView recyclerView) { boolean removeSuccess = mRecyclerViews.remove(recyclerView); - if (RecyclerView.DEBUG && !removeSuccess) { + if (RecyclerView.sDebugAssertionsEnabled && !removeSuccess) { throw new IllegalStateException("RecyclerView removal failed!"); } } @@ -175,7 +175,7 @@ final class GapWorker implements Runnable { */ void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) { if (recyclerView.isAttachedToWindow()) { - if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) { + if (RecyclerView.sDebugAssertionsEnabled && !mRecyclerViews.contains(recyclerView)) { throw new IllegalStateException("attempting to post unregistered view!"); } if (mPostTimeNs == 0) { diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java index df4de2d5e6f..9265d56d01e 100644 --- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java +++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java @@ -219,7 +219,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, static final String TAG = "RecyclerView"; - static final boolean DEBUG = false; + static boolean sDebugAssertionsEnabled = false; + static boolean sVerboseLoggingEnabled = false; static final boolean VERBOSE_TRACING = false; @@ -386,6 +387,35 @@ public class RecyclerView extends ViewGroup implements ScrollingView, private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class, AttributeSet.class, int.class, int.class}; + /** + * Enable internal assertions about RecyclerView's state and throw exceptions if the + * assertions are violated. + * <p> + * This is primarily intended to diagnose problems with RecyclerView, and + * <strong>should not be enabled in production</strong> unless you have a specific reason to + * do so. + * <p> + * Enabling this may negatively affect performance and/or stability. + * + * @param debugAssertionsEnabled true to enable assertions; false to disable them + */ + public static void setDebugAssertionsEnabled(boolean debugAssertionsEnabled) { + RecyclerView.sDebugAssertionsEnabled = debugAssertionsEnabled; + } + + /** + * Enable verbose logging within RecyclerView itself. + * <p> + * Enabling this may negatively affect performance and reduce the utility of logcat due to + * high-volume logging. This generally <strong>should not be enabled in production</strong> + * unless you have a specific reason for doing so. + * + * @param verboseLoggingEnabled true to enable logging; false to disable it + */ + public static void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) { + RecyclerView.sVerboseLoggingEnabled = verboseLoggingEnabled; + } + private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); final Recycler mRecycler = new Recycler(); @@ -983,10 +1013,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView, throw new IllegalArgumentException("Called attach on a child which is not" + " detached: " + vh + exceptionLabel()); } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "reAttach " + vh); } vh.clearTmpDetachFlag(); + } else { + if (sDebugAssertionsEnabled) { + throw new IllegalArgumentException( + "No ViewHolder found for child: " + child + ", index: " + index + + exceptionLabel()); + } } RecyclerView.this.attachViewToParent(child, index, layoutParams); } @@ -1001,11 +1037,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView, throw new IllegalArgumentException("called detach on an already" + " detached child " + vh + exceptionLabel()); } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "tmpDetach " + vh); } vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); } + } else { + if (sDebugAssertionsEnabled) { + throw new IllegalArgumentException( + "No view at offset " + offset + exceptionLabel()); + } } RecyclerView.this.detachViewFromParent(offset); } @@ -1039,7 +1080,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, // ensure it is not hidden because for adapter helper, the only thing matter is that // LM thinks view is a child. if (mChildHelper.isHidden(vh.itemView)) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "assuming view holder cannot be find because it is hidden"); } return null; @@ -1554,7 +1595,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, final ViewHolder viewHolder = getChildViewHolderInt(view); mRecycler.unscrapView(viewHolder); mRecycler.recycleViewHolderInternal(viewHolder); - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "after removing animated view: " + view + ", " + this); } } @@ -1638,7 +1679,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, if (state == mScrollState) { return; } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, new Exception()); } @@ -2401,7 +2442,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, void stopInterceptRequestLayout(boolean performLayoutChildren) { if (mInterceptRequestLayoutDepth < 1) { //noinspection PointlessBooleanExpression - if (DEBUG) { + if (sDebugAssertionsEnabled) { throw new IllegalStateException("stopInterceptRequestLayout was called more " + "times than startInterceptRequestLayout." + exceptionLabel()); @@ -4099,7 +4140,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, void onExitLayoutOrScroll(boolean enableChangeEvents) { mLayoutOrScrollCounter--; if (mLayoutOrScrollCounter < 1) { - if (DEBUG && mLayoutOrScrollCounter < 0) { + if (sDebugAssertionsEnabled && mLayoutOrScrollCounter < 0) { throw new IllegalStateException("layout or scroll counter cannot go below zero." + "Some calls are not matching" + exceptionLabel()); } @@ -4808,6 +4849,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, throw new IllegalArgumentException("Called removeDetachedView with a view which" + " is not flagged as tmp detached." + vh + exceptionLabel()); } + } else { + if (sDebugAssertionsEnabled) { + throw new IllegalArgumentException( + "No ViewHolder found for child: " + child + exceptionLabel()); + } } // Clear any android.view.animation.Animation that may prevent the item from @@ -5009,7 +5055,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { + if (sDebugAssertionsEnabled && holder.mPosition == -1 && !holder.isRemoved()) { throw new IllegalStateException("view holder cannot have position -1 unless it" + " is removed" + exceptionLabel()); } @@ -5048,7 +5094,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, if (holder == null || holder.mPosition < start || holder.mPosition > end) { continue; } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " + holder); } @@ -5069,7 +5115,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + holder + " now at position " + (holder.mPosition + itemCount)); } @@ -5089,7 +5135,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore()) { if (holder.mPosition >= positionEnd) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + " holder " + holder + " now at position " + (holder.mPosition - itemCount)); @@ -5097,7 +5143,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, holder.offsetPosition(-itemCount, applyToPreLayout); mState.mStructureChanged = true; } else if (holder.mPosition >= positionStart) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + " holder " + holder + " now REMOVED"); } @@ -6278,7 +6324,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, PoolingContainer.callPoolingContainerOnRelease(scrap.itemView); return; } - if (DEBUG && scrapHeap.contains(scrap)) { + if (sDebugAssertionsEnabled && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); @@ -6533,7 +6579,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, // if it is a removed holder, nothing to verify since we cannot ask adapter anymore // if it is not removed, verify the type and id. if (holder.isRemoved()) { - if (DEBUG && !mState.isPreLayout()) { + if (sDebugAssertionsEnabled && !mState.isPreLayout()) { throw new IllegalStateException("should not receive a removed view unless it" + " is pre layout" + exceptionLabel()); } @@ -6579,7 +6625,30 @@ public class RecyclerView extends ViewGroup implements ScrollingView, // abort - we have a deadline we can't meet return false; } + + // Holders being bound should be either fully attached or fully detached. + // We don't want to bind with views that are temporarily detached, because that + // creates a situation in which they are unable to reason about their attach state + // properly. + // For example, isAttachedToWindow will return true, but the itemView will lack a + // parent. This breaks, among other possible issues, anything involving traversing + // the view tree, such as ViewTreeLifecycleOwner. + // Thus, we temporarily reattach any temp-detached holders for the bind operation. + // See https://issuetracker.google.com/265347515 for additional details on problems + // resulting from this + boolean reattachedForBind = false; + if (holder.isTmpDetached()) { + attachViewToParent(holder.itemView, getChildCount(), + holder.itemView.getLayoutParams()); + reattachedForBind = true; + } + mAdapter.bindViewHolder(holder, offsetPosition); + + if (reattachedForBind) { + detachViewFromParent(holder.itemView); + } + long endBindNs = getNanoTime(); mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); attachAccessibilityDelegateOnBind(holder); @@ -6782,7 +6851,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, } } if (holder == null) { // fallback to pool - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } @@ -6812,7 +6881,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } @@ -6839,7 +6908,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { - if (DEBUG && holder.isRemoved()) { + if (sDebugAssertionsEnabled && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel()); @@ -6978,11 +7047,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, * @param cachedViewIndex The index of the view in cached views list */ void recycleCachedViewAt(int cachedViewIndex) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); } ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); } addViewHolderToRecycledViewPool(viewHolder, true); @@ -7020,7 +7089,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, && mAdapter.onFailedToRecycleView(holder); boolean cached = false; boolean recycled = false; - if (DEBUG && mCachedViews.contains(holder)) { + if (sDebugAssertionsEnabled && mCachedViews.contains(holder)) { throw new IllegalArgumentException("cached view received recycle internal? " + holder + exceptionLabel()); } @@ -7066,7 +7135,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, // TODO: consider cancelling an animation when an item is removed scrollBy, // to return it to the pool faster - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " + "re-visit here. We are still removing it from animation lists" + exceptionLabel()); @@ -7268,7 +7337,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, if (!dryRun) { mCachedViews.remove(i); } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position + ") found match in cache: " + holder); } @@ -7348,7 +7417,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, if (mState != null) { mViewInfoStore.removeViewHolder(holder); } - if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); + if (sVerboseLoggingEnabled) Log.d(TAG, "dispatchViewRecycled: " + holder); } void onAdapterChanged(Adapter<?> oldAdapter, Adapter<?> newAdapter, @@ -7382,7 +7451,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, } else { holder.offsetPosition(inBetweenOffset, false); } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " + holder); } @@ -7394,7 +7463,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, for (int i = 0; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get(i); if (holder != null && holder.mPosition >= insertedAt) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + holder + " now at position " + (holder.mPosition + count)); } @@ -7417,7 +7486,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, final ViewHolder holder = mCachedViews.get(i); if (holder != null) { if (holder.mPosition >= removedEnd) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + " holder " + holder + " now at position " + (holder.mPosition - count)); @@ -7758,6 +7827,23 @@ public class RecyclerView extends ViewGroup implements ScrollingView, TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); } holder.mBindingAdapter = this; + if (sDebugAssertionsEnabled) { + if (holder.itemView.getParent() == null + && (ViewCompat.isAttachedToWindow(holder.itemView) + != holder.isTmpDetached())) { + throw new IllegalStateException("Temp-detached state out of sync with reality. " + + "holder.isTmpDetached(): " + holder.isTmpDetached() + + ", attached to window: " + + ViewCompat.isAttachedToWindow(holder.itemView) + + ", holder: " + holder); + } + if (holder.itemView.getParent() == null + && ViewCompat.isAttachedToWindow(holder.itemView)) { + throw new IllegalStateException( + "Attempting to bind attached holder with no parent" + + " (AKA temp detached): " + holder); + } + } onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); if (rootBind) { holder.clearPayload(); @@ -9167,7 +9253,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, * @param position Scroll to this adapter position. */ public void scrollToPosition(int position) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); } } @@ -9348,7 +9434,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, } } if (lp.mPendingInvalidate) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); } holder.itemView.invalidate(); @@ -9940,7 +10026,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.shouldIgnore()) { - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "ignoring view " + viewHolder); } return; @@ -12149,6 +12235,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, } void resetInternal() { + if (sDebugAssertionsEnabled && isTmpDetached()) { + throw new IllegalStateException("Attempting to reset temp-detached ViewHolder: " + + this + ". ViewHolders should be fully detached before resetting."); + } + mFlags = 0; mPosition = NO_POSITION; mOldPosition = NO_POSITION; @@ -12228,7 +12319,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; if (mIsRecyclableCount < 0) { mIsRecyclableCount = 0; - if (DEBUG) { + if (sDebugAssertionsEnabled) { throw new RuntimeException("isRecyclable decremented below 0: " + "unmatched pair of setIsRecyable() calls for " + this); } @@ -12239,7 +12330,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, } else if (recyclable && mIsRecyclableCount == 0) { mFlags &= ~FLAG_NOT_RECYCLABLE; } - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); } } @@ -12834,7 +12925,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, protected void onChildAttachedToWindow(View child) { if (getChildPosition(child) == getTargetPosition()) { mTargetView = child; - if (DEBUG) { + if (sVerboseLoggingEnabled) { Log.d(TAG, "smooth scroll target view has been attached"); } } diff --git a/settings.gradle b/settings.gradle index 1a410fc3536..0dc9f3a93ce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -358,11 +358,11 @@ includeProject(":annotation:annotation-experimental") includeProject(":annotation:annotation-experimental-lint") includeProject(":annotation:annotation-experimental-lint-integration-tests", "annotation/annotation-experimental-lint/integration-tests") includeProject(":annotation:annotation-sampled") -includeProject(":appcompat:appcompat", [BuildType.MAIN]) +includeProject(":appcompat:appcompat", [BuildType.MAIN, BuildType.COMPOSE]) includeProject(":appcompat:appcompat-benchmark", [BuildType.MAIN]) -includeProject(":appcompat:appcompat-lint", [BuildType.MAIN]) -includeProject(":appcompat:appcompat-lint:integration-tests", [BuildType.MAIN]) -includeProject(":appcompat:appcompat-resources", [BuildType.MAIN]) +includeProject(":appcompat:appcompat-lint", [BuildType.MAIN, BuildType.COMPOSE]) +includeProject(":appcompat:appcompat-lint:integration-tests", [BuildType.MAIN, BuildType.COMPOSE]) +includeProject(":appcompat:appcompat-resources", [BuildType.MAIN, BuildType.COMPOSE]) includeProject(":appcompat:integration-tests:receive-content-testapp", [BuildType.MAIN]) includeProject(":appsearch:appsearch", [BuildType.MAIN]) includeProject(":appsearch:appsearch-builtin-types", [BuildType.MAIN]) @@ -874,7 +874,7 @@ includeProject(":versionedparcelable:versionedparcelable", [BuildType.MAIN]) includeProject(":versionedparcelable:versionedparcelable-compiler", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR]) includeProject(":viewpager2:integration-tests:testapp", [BuildType.MAIN]) includeProject(":viewpager2:integration-tests:targetsdk-tests", [BuildType.MAIN]) -includeProject(":viewpager2:viewpager2", [BuildType.MAIN]) +includeProject(":viewpager2:viewpager2", [BuildType.MAIN, BuildType.COMPOSE]) includeProject(":viewpager:viewpager", [BuildType.MAIN]) includeProject(":wear:wear", [BuildType.MAIN, BuildType.WEAR]) includeProject(":wear:benchmark:integration-tests:macrobenchmark-target", [BuildType.MAIN, BuildType.COMPOSE]) @@ -978,7 +978,7 @@ includeProject(":support-wear-demos", new File(samplesRoot, "SupportWearDemos"), includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN]) includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN, BuildType.KMP]) includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.MEDIA, BuildType.WEAR, BuildType.CAMERA]) -includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN]) +includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN, BuildType.COMPOSE]) includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN, BuildType.COMPOSE]) includeProject(":internal-testutils-fonts", "testutils/testutils-fonts", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR]) includeProject(":internal-testutils-truth", "testutils/testutils-truth", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR, BuildType.KMP, BuildType.CAMERA]) diff --git a/viewpager2/integration-tests/targetsdk-tests/src/androidTest/kotlin/androidx/viewpager2/integration/targetsdktests/OnApplyWindowInsetsListenerTest.kt b/viewpager2/integration-tests/targetsdk-tests/src/androidTest/kotlin/androidx/viewpager2/integration/targetsdktests/OnApplyWindowInsetsListenerTest.kt index 70aa8ed5ad6..a143dbe5437 100644 --- a/viewpager2/integration-tests/targetsdk-tests/src/androidTest/kotlin/androidx/viewpager2/integration/targetsdktests/OnApplyWindowInsetsListenerTest.kt +++ b/viewpager2/integration-tests/targetsdk-tests/src/androidTest/kotlin/androidx/viewpager2/integration/targetsdktests/OnApplyWindowInsetsListenerTest.kt @@ -48,7 +48,7 @@ import org.junit.runners.Parameterized import java.lang.reflect.Field @LargeTest -@SdkSuppress(minSdkVersion = 21) +@SdkSuppress(minSdkVersion = 30) // TODO(b/273945673): fix test on API 21..30 @RunWith(Parameterized::class) class OnApplyWindowInsetsListenerTest(private val config: TestConfig) { data class TestConfig( @@ -226,7 +226,7 @@ class OnApplyWindowInsetsListenerTest(private val config: TestConfig) { } private fun createWindowInsets(): WindowInsetsCompat { - val insets = Insets.of(10, 10, 10, 10) + val insets = Insets.of(10, 11, 12, 13) @Suppress("DEPRECATION") val windowInsets = WindowInsetsCompat.Builder().setSystemWindowInsets(insets).build() if (Build.VERSION.SDK_INT < 29) { diff --git a/viewpager2/viewpager2/api/current.txt b/viewpager2/viewpager2/api/current.txt index 111eb2ff0ed..997ce2a9d79 100644 --- a/viewpager2/viewpager2/api/current.txt +++ b/viewpager2/viewpager2/api/current.txt @@ -24,7 +24,6 @@ package androidx.viewpager2.adapter { method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment); - method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment); } public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener { diff --git a/viewpager2/viewpager2/api/public_plus_experimental_current.txt b/viewpager2/viewpager2/api/public_plus_experimental_current.txt index 111eb2ff0ed..63ac8cbbf8d 100644 --- a/viewpager2/viewpager2/api/public_plus_experimental_current.txt +++ b/viewpager2/viewpager2/api/public_plus_experimental_current.txt @@ -19,12 +19,15 @@ package androidx.viewpager2.adapter { method public void unregisterFragmentTransactionCallback(androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback); } + @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface FragmentStateAdapter.ExperimentalFragmentStateAdapterApi { + } + public abstract static class FragmentStateAdapter.FragmentTransactionCallback { ctor public FragmentStateAdapter.FragmentTransactionCallback(); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment); - method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment); + method @androidx.viewpager2.adapter.FragmentStateAdapter.ExperimentalFragmentStateAdapterApi public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment); } public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener { diff --git a/viewpager2/viewpager2/api/restricted_current.txt b/viewpager2/viewpager2/api/restricted_current.txt index 9316cd0e623..4aab4bd3c8a 100644 --- a/viewpager2/viewpager2/api/restricted_current.txt +++ b/viewpager2/viewpager2/api/restricted_current.txt @@ -24,7 +24,6 @@ package androidx.viewpager2.adapter { method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment); method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment); - method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment); } public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener { diff --git a/viewpager2/viewpager2/build.gradle b/viewpager2/viewpager2/build.gradle index 5d007b677ae..8ca56085d1a 100644 --- a/viewpager2/viewpager2/build.gradle +++ b/viewpager2/viewpager2/build.gradle @@ -24,9 +24,10 @@ plugins { dependencies { api("androidx.annotation:annotation:1.1.0") + api("androidx.annotation:annotation-experimental:1.3.0") implementation("androidx.core:core:1.3.2") api("androidx.fragment:fragment:1.1.0") - api("androidx.recyclerview:recyclerview:1.2.0") + api(project(":recyclerview:recyclerview")) // TODO(191302495): pin to RV 1.3.1 once in prebuilts implementation("androidx.collection:collection:1.1.0") androidTestImplementation(libs.testUiautomator) diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt index a81994ef842..25341c26936 100644 --- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt +++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt @@ -161,19 +161,18 @@ open class BaseTest { field = value } - fun runOnUiThreadSync(f: () -> Unit) { + fun <T> runOnUiThreadSync(f: () -> T): T { var thrownError: Throwable? = null + var result: T? = null activityTestRule.runOnUiThread { try { - f() + result = f() } catch (t: Throwable) { thrownError = t } } - val caughtError = thrownError - if (caughtError != null) { - throw caughtError - } + thrownError?.let { throw it } + return result!! } val viewPager: ViewPager2 get() = activity.findViewById(R.id.view_pager) diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt index eca228fcba0..8d70eefd038 100644 --- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt +++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt @@ -16,6 +16,7 @@ package androidx.viewpager2.widget +import android.os.Build import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment @@ -180,30 +181,57 @@ class FragmentTransactionCallbackTest : BaseTest() { assertThat( log.consume().filter { !it.contains("onFragmentSaveInstanceState") }, equalTo( - listOf( - "Lifecycle:onFragmentPaused(f1)", - "Lifecycle:onFragmentStopped(f0)", - "Lifecycle:onFragmentStopped(f1)", - // "Lifecycle:onFragmentSaveInstanceState(f0)", # unstable ordering - // "Lifecycle:onFragmentSaveInstanceState(f1)", # unstable ordering - "Lifecycle:onFragmentViewDestroyed(f0)", - "Lifecycle:onFragmentDestroyed(f0)", - "Lifecycle:onFragmentDetached(f0)", - "Lifecycle:onFragmentViewDestroyed(f1)", - "Lifecycle:onFragmentDestroyed(f1)", - "Lifecycle:onFragmentDetached(f1)", - "Lifecycle:onFragmentViewCreated(f0)", - "Lifecycle:onFragmentActivityCreated(f0)", - "Lifecycle:onFragmentViewCreated(f1)", - "Lifecycle:onFragmentActivityCreated(f1)", - "Lifecycle:onFragmentStarted(f0)", - "Lifecycle:onFragmentStarted(f1)", - "Adapter:onFragmentMaxLifecyclePreUpdated(f0 at STARTED)", - "Adapter:onFragmentMaxLifecyclePreUpdated(f1 at RESUMED)", - "Adapter:onFragmentMaxLifecycleUpdated(f1 at RESUMED)", - "Adapter:onFragmentMaxLifecycleUpdated(f0 at STARTED)", - "Lifecycle:onFragmentResumed(f1)" - ) + when (Build.VERSION.SDK_INT) { + in 1..28 -> listOf( + "Lifecycle:onFragmentPaused(f1)", + "Lifecycle:onFragmentStopped(f0)", + "Lifecycle:onFragmentStopped(f1)", + // "Lifecycle:onFragmentSaveInstanceState(f0)", # unstable ordering + // "Lifecycle:onFragmentSaveInstanceState(f1)", # unstable ordering + "Lifecycle:onFragmentViewDestroyed(f0)", + "Lifecycle:onFragmentDestroyed(f0)", + "Lifecycle:onFragmentDetached(f0)", + "Lifecycle:onFragmentViewDestroyed(f1)", + "Lifecycle:onFragmentDestroyed(f1)", + "Lifecycle:onFragmentDetached(f1)", + "Lifecycle:onFragmentViewCreated(f0)", + "Lifecycle:onFragmentActivityCreated(f0)", + "Lifecycle:onFragmentViewCreated(f1)", + "Lifecycle:onFragmentActivityCreated(f1)", + "Lifecycle:onFragmentStarted(f0)", + "Lifecycle:onFragmentStarted(f1)", + "Adapter:onFragmentMaxLifecyclePreUpdated(f0 at STARTED)", + "Adapter:onFragmentMaxLifecyclePreUpdated(f1 at RESUMED)", + "Adapter:onFragmentMaxLifecycleUpdated(f1 at RESUMED)", + "Adapter:onFragmentMaxLifecycleUpdated(f0 at STARTED)", + "Lifecycle:onFragmentResumed(f1)" + ) + // TODO(b/266975014): investigate change in behaviour on API 29+ + else -> listOf( + "Lifecycle:onFragmentPaused(f1)", + "Lifecycle:onFragmentStopped(f0)", + "Lifecycle:onFragmentStopped(f1)", + // "Lifecycle:onFragmentSaveInstanceState(f0)", # unstable ordering + // "Lifecycle:onFragmentSaveInstanceState(f1)", # unstable ordering + "Lifecycle:onFragmentViewDestroyed(f0)", + "Lifecycle:onFragmentDestroyed(f0)", + "Lifecycle:onFragmentDetached(f0)", + "Lifecycle:onFragmentViewDestroyed(f1)", + "Lifecycle:onFragmentDestroyed(f1)", + "Lifecycle:onFragmentDetached(f1)", + "Lifecycle:onFragmentViewCreated(f0)", + "Lifecycle:onFragmentActivityCreated(f0)", + "Lifecycle:onFragmentViewCreated(f1)", + "Lifecycle:onFragmentActivityCreated(f1)", + "Lifecycle:onFragmentStarted(f0)", + "Lifecycle:onFragmentStarted(f1)", + "Lifecycle:onFragmentResumed(f1)", + "Adapter:onFragmentMaxLifecyclePreUpdated(f0 at STARTED)", + "Adapter:onFragmentMaxLifecyclePreUpdated(f1 at RESUMED)", + "Adapter:onFragmentMaxLifecycleUpdated(f1 at RESUMED)", + "Adapter:onFragmentMaxLifecycleUpdated(f0 at STARTED)" + ) + } ) ) diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt index 8a57fd66fd0..678d33be7ad 100644 --- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt +++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt @@ -63,8 +63,8 @@ class HostFragmentBackStackTest : BaseTest() { activity.setContentView(container) } - val viewPagerFragment = ViewPagerFragment() - val blankFragment = Fragment() + val viewPagerFragment = runOnUiThreadSync { ViewPagerFragment() } + val blankFragment = runOnUiThreadSync { Fragment() } fun setActiveFragment(f: Fragment, targetPage: Int? = null) { // set new active fragment diff --git a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java index a0ec29cb053..7a61aece608 100644 --- a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java +++ b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java @@ -34,6 +34,8 @@ import android.widget.FrameLayout; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; +import androidx.annotation.RequiresOptIn; import androidx.collection.ArraySet; import androidx.collection.LongSparseArray; import androidx.core.view.ViewCompat; @@ -183,22 +185,10 @@ public abstract class FragmentStateAdapter extends ensureFragment(position); /** Special case when {@link RecyclerView} decides to keep the {@link container} - * attached to the window, but not to the view hierarchy (i.e. parent is null) */ + * attached to the window, resulting in no {@link `onViewAttachedToWindow} callback later */ final FrameLayout container = holder.getContainer(); if (ViewCompat.isAttachedToWindow(container)) { - if (container.getParent() != null) { - throw new IllegalStateException("Design assumption violated."); - } - container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (container.getParent() != null) { - container.removeOnLayoutChangeListener(this); - placeFragmentInViewHolder(holder); - } - } - }); + placeFragmentInViewHolder(holder); } gcFragments(); @@ -842,6 +832,7 @@ public abstract class FragmentStateAdapter extends return result; } + @OptIn(markerClass = ExperimentalFragmentStateAdapterApi.class) public List<OnPostEventListener> dispatchPreSavedInstanceState(Fragment fragment) { List<OnPostEventListener> result = new ArrayList<>(); for (FragmentTransactionCallback callback : mCallbacks) { @@ -890,6 +881,7 @@ public abstract class FragmentStateAdapter extends * @return Listener called after the operation */ @NonNull + @ExperimentalFragmentStateAdapterApi // Experimental in v1.1.*. To become stable in v1.2.*. public OnPostEventListener onFragmentPreSavedInstanceState(@NonNull Fragment fragment) { return NO_OP; } @@ -949,4 +941,7 @@ public abstract class FragmentStateAdapter extends @NonNull FragmentTransactionCallback callback) { mFragmentEventDispatcher.unregisterCallback(callback); } + + @RequiresOptIn(level = RequiresOptIn.Level.WARNING) + public @interface ExperimentalFragmentStateAdapterApi { } } diff --git a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/widget/WindowInsetsApplier.java b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/widget/WindowInsetsApplier.java index 2d58541772d..5527cc0e9f6 100644 --- a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/widget/WindowInsetsApplier.java +++ b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/widget/WindowInsetsApplier.java @@ -111,6 +111,7 @@ public final class WindowInsetsApplier implements OnApplyWindowInsetsListener { return consumeAllInsets(applied); } + @SuppressWarnings("deprecation") // consumeSystemWindowInsets, consumeStableInsets private WindowInsetsCompat consumeAllInsets(@NonNull WindowInsetsCompat insets) { if (Build.VERSION.SDK_INT >= 21) { if (WindowInsetsCompat.CONSUMED.toWindowInsets() != null) { |