aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-05-17 06:06:27 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-05-17 06:06:27 +0000
commit346440fe4046777c435e51ab7c1f9719d024bc9b (patch)
tree70d659498f446356fd8e68cdec6355dfc2be11d2
parent37f2f2effd10f4fdd34273160a2ef3fafecd3442 (diff)
parentd52bd339fd007893bf109ce02fe55aeb53d0ade7 (diff)
downloadsupport-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
-rw-r--r--libraryversions.toml2
-rw-r--r--recyclerview/recyclerview/api/current.txt2
-rw-r--r--recyclerview/recyclerview/api/public_plus_experimental_current.txt2
-rw-r--r--recyclerview/recyclerview/api/restricted_current.txt2
-rw-r--r--recyclerview/recyclerview/build.gradle5
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt33
-rw-r--r--recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java13
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GapWorker.java6
-rw-r--r--recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java157
-rw-r--r--settings.gradle12
-rw-r--r--viewpager2/integration-tests/targetsdk-tests/src/androidTest/kotlin/androidx/viewpager2/integration/targetsdktests/OnApplyWindowInsetsListenerTest.kt4
-rw-r--r--viewpager2/viewpager2/api/current.txt1
-rw-r--r--viewpager2/viewpager2/api/public_plus_experimental_current.txt5
-rw-r--r--viewpager2/viewpager2/api/restricted_current.txt1
-rw-r--r--viewpager2/viewpager2/build.gradle3
-rw-r--r--viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt11
-rw-r--r--viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt76
-rw-r--r--viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt4
-rw-r--r--viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java23
-rw-r--r--viewpager2/viewpager2/src/main/java/androidx/viewpager2/widget/WindowInsetsApplier.java1
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) {