1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
3999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
4999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * you may not use this file except in compliance with the License.
6999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * You may obtain a copy of the License at
7999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
8999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar *
10999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Unless required by applicable law or agreed to in writing, software
11999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * See the License for the specific language governing permissions and
14999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * limitations under the License.
15999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */
16ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
17999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
18e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
19e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
21c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
22c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
23c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
24e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertEquals;
253911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurskaimport static org.junit.Assert.assertFalse;
26e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertNotNull;
27e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
28e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static java.util.concurrent.TimeUnit.SECONDS;
29e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
30999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.content.Context;
31999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect;
32999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log;
33999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.view.View;
344143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport android.view.ViewGroup;
35999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
36c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.Nullable;
37c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
38e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.CoreMatchers;
39e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.MatcherAssert;
40e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
41999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.lang.reflect.Field;
42999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.ArrayList;
43999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.LinkedHashMap;
44999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List;
45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map;
46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.CountDownLatch;
47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.TimeUnit;
48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final boolean DEBUG = false;
52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final String TAG = "LinearLayoutManagerTest";
53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> createBaseVariations() {
55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> variations = new ArrayList<>();
56999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
594143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    for (boolean wrap : new boolean[]{false, true}) {
604143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        variations.add(
614143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                                new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap));
624143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    }
634143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return variations;
68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    TestAdapter mTestAdapter;
72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Object... variations)
75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Config config : base) {
79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (Object variation : variations) {
80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Config newConfig = (Config) config.clone();
81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                field.set(newConfig, variation);
82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                newConfigs.add(newConfig);
83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return newConfigs;
86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
893911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        setupByConfig(config, waitForFirstLayout, null, null);
903911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska    }
913911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
923911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska    void setupByConfig(Config config, boolean waitForFirstLayout,
933911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        @Nullable RecyclerView.LayoutParams childLayoutParams,
943911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        @Nullable RecyclerView.LayoutParams parentLayoutParams) throws Throwable {
95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView = inflateWrappedRV();
96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
97999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setHasFixedSize(true);
983911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        mTestAdapter = config.mTestAdapter == null
993911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                ? new TestAdapter(config.mItemCount, childLayoutParams)
100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                : config.mTestAdapter;
101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
1033911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            config.mReverseLayout);
104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
1074143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        if (config.mWrap) {
1084143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mRecyclerView.setLayoutParams(
1094143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    new ViewGroup.LayoutParams(
110e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                            config.mOrientation == HORIZONTAL ? WRAP_CONTENT : MATCH_PARENT,
111e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                            config.mOrientation == VERTICAL ? WRAP_CONTENT : MATCH_PARENT
1124143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    )
1134143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            );
1144143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
1153911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        if (parentLayoutParams != null) {
1163911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            mRecyclerView.setLayoutParams(parentLayoutParams);
1173911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        }
1183911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (waitForFirstLayout) {
120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            waitForFirstLayout();
121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws Throwable {
126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (state.isPreLayout()) {
132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("pending scroll position should still be pending",
133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } else {
139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    RecyclerView.ViewHolder vh =
140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertNotNull("scroll to position should work", vh);
142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("scroll offset should be applied properly",
144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset +
145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                        ((RecyclerView.LayoutParams) vh.itemView
146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                                .getLayoutParams()).topMargin,
147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        };
152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(2);
15342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                try {
157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mTestAdapter.addAndNotify(0, 1);
158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    } else {
161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset);
163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } catch (Throwable throwable) {
166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    throwable.printStackTrace();
167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        checkForMainThreadException();
173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected void waitForFirstLayout() throws Throwable {
176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(1);
177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setRecyclerView(mRecyclerView);
178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
18242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Map<Item, Rect> after, boolean strictItemEquality) {
192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Throwable throwable = null;
193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        try {
194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertRectSetsEqual("NOT " + message, before, after, strictItemEquality);
195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        } catch (Throwable t) {
196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throwable = t;
197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertRectSetsEqual(message, before, after, true);
203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            boolean strictItemEquality) {
207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        StringBuilder sb = new StringBuilder();
208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("checking rectangle equality.\n");
209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("before:\n");
210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("after:\n");
214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        message = message + "\n" + sb.toString();
218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                , after.size());
220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Item beforeItem = entry.getKey();
222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Rect afterRect = null;
223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (strictItemEquality) {
224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                afterRect = after.get(beforeItem);
225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nSame item should be visible after simple re-layout",
226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final Item afterItem = afterEntry.getKey();
230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect = afterEntry.getValue();
232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        break;
233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
235999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nItem with same adapter index should be visible " +
236999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                "after simple re-layout",
237999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
238999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
239999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
240999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    entry.getValue(), afterRect);
241999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
242999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
243999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
244999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class VisibleChildren {
245999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
246999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
247999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
248999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
249999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
250999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
251999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
252999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
253999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
254999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
255999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
256999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return "VisibleChildren{" +
257999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
258999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
259999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
260999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
261999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    '}';
262999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
263999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
264999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
265999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class OnLayoutListener {
266999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
267999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
268999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
269999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
270999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
271999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
272999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
273999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
274999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class Config implements Cloneable {
275999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
276c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        static final int DEFAULT_ITEM_COUNT = 250;
277999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
278999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mStackFromEnd;
279999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
280999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mOrientation = VERTICAL;
281999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
282999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mReverseLayout = false;
283999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
284999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
285999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
286999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
287999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
2884143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        boolean mWrap = false;
2894143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
290999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        TestAdapter mTestAdapter;
291999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
292999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
293999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
294999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
295999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromEnd;
296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config() {
299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config adapter(TestAdapter adapter) {
303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mTestAdapter = adapter;
304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config orientation(int orientation) {
313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromBottom;
319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config itemCount(int itemCount) {
328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mItemCount = itemCount;
329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
332999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // required by convention
333999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
334999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Object clone() throws CloneNotSupportedException {
335999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return super.clone();
336999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
337999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
338999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
339999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
340210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas            return "Config{"
341210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + "mStackFromEnd=" + mStackFromEnd
342210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + ",mOrientation=" + mOrientation
343210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + ",mReverseLayout=" + mReverseLayout
344210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + ",mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach
345210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + ",mItemCount=" + mItemCount
346210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + ",wrap=" + mWrap
347210acb1081c292500390aaeb7a3eba249933dd9fAurimas Liutikas                    + '}';
348999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
3494143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
3504143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        public Config wrap(boolean wrap) {
3514143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mWrap = wrap;
3524143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            return this;
3534143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
354999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
355999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
356999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
357999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
358999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        CountDownLatch layoutLatch;
359c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        CountDownLatch snapLatch;
360ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        CountDownLatch prefetchLatch;
3613911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        CountDownLatch callbackLatch;
362ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
363999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OrientationHelper mSecondaryOrientation;
364999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
365999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OnLayoutListener mOnLayoutListener;
366999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
3673911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        RecyclerView.OnScrollListener mCallbackListener = new RecyclerView.OnScrollListener() {
3683911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
3693911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            @Override
3703911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
3713911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                super.onScrollStateChanged(recyclerView, newState);
3723911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                callbackLatch.countDown();
3733911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                if (callbackLatch.getCount() == 0L) {
3743911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                    removeOnScrollListener(this);
3753911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                }
3763911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            }
3773911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        };
3783911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
379999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
380999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super(context, orientation, reverseLayout);
381999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
382999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
383999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void expectLayouts(int count) {
384999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch = new CountDownLatch(count);
385999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
386999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
3873911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        public void expectCallbacks(int count) throws Throwable {
3883911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            callbackLatch = new CountDownLatch(count);
3893911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            mRecyclerView.addOnScrollListener(mCallbackListener);
3903911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        }
3913911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
3923911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        private void removeOnScrollListener(RecyclerView.OnScrollListener listener) {
3933911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            mRecyclerView.removeOnScrollListener(listener);
3943911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        }
3953911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
39666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        public void waitForLayout(int seconds) throws Throwable {
39766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
39866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            checkForMainThreadException();
39966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            MatcherAssert.assertThat("all layouts should complete on time",
40066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    layoutLatch.getCount(), CoreMatchers.is(0L));
40166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            // use a runnable to ensure RV layout is finished
40266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            getInstrumentation().runOnMainSync(new Runnable() {
40366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                @Override
40466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                public void run() {
40566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                }
40666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            });
407999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
408999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
4093911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        public void assertNoCallbacks(String msg, long timeout) throws Throwable {
4103911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            callbackLatch.await(timeout, TimeUnit.SECONDS);
4113911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            long latchCount = callbackLatch.getCount();
4123911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            assertFalse(msg + " :" + latchCount, latchCount == 0);
4133911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska            removeOnScrollListener(mCallbackListener);
4143911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska        }
4153911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska
416ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        public void expectPrefetch(int count) {
417ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            prefetchLatch = new CountDownLatch(count);
418ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
419ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
420ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        public void waitForPrefetch(int seconds) throws Throwable {
421ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            prefetchLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
422ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            checkForMainThreadException();
423ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            MatcherAssert.assertThat("all prefetches should complete on time",
424ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                    prefetchLatch.getCount(), CoreMatchers.is(0L));
425ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            // use a runnable to ensure RV layout is finished
426ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            getInstrumentation().runOnMainSync(new Runnable() {
427ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                @Override
428ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                public void run() {
429ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                }
430ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            });
431ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
432ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
433c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        public void expectIdleState(int count) {
434c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            snapLatch = new CountDownLatch(count);
435c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
436c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                @Override
437c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
438c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    super.onScrollStateChanged(recyclerView, newState);
439c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
440c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        snapLatch.countDown();
441c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        if (snapLatch.getCount() == 0L) {
442c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                            mRecyclerView.removeOnScrollListener(this);
443c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        }
444c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    }
445c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                }
446c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            });
447c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        }
448c587f7dba5a337169e854e235da59f595255d6ccAga Madurska
449c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        public void waitForSnap(int seconds) throws Throwable {
450c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            snapLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
451c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            checkForMainThreadException();
452c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            MatcherAssert.assertThat("all scrolling should complete on time",
453c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    snapLatch.getCount(), CoreMatchers.is(0L));
454c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            // use a runnable to ensure RV layout is finished
455c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            getInstrumentation().runOnMainSync(new Runnable() {
456c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                @Override
4573911e1c2d38e301d5ffbdf11f808fdc593dd83e9Aga Madurska                public void run() {}
458c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            });
459c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        }
460c587f7dba5a337169e854e235da59f595255d6ccAga Madurska
461999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
462999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void setOrientation(int orientation) {
463999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.setOrientation(orientation);
464999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mSecondaryOrientation = null;
465999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
466999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
467999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
468999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
469999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
470999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
471999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
472999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleView(child, recycler);
473999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
474999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
475999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
476999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
477999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
478999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG,
479999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
480999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
481999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleViewAt(index, recycler);
482999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
483999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
484999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
485999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void ensureLayoutState() {
486999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.ensureLayoutState();
487999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (mSecondaryOrientation == null) {
488999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
489999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        1 - getOrientation());
490999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
491999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
492999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
493999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
494999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        LayoutState createLayoutState() {
495999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return new LayoutState() {
496999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
497999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View next(RecyclerView.Recycler recycler) {
498999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final boolean hadMore = hasMore(mRecyclerView.mState);
499999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int position = mCurrentPosition;
500999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    View next = super.next(recycler);
501999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("if has more, should return a view", hadMore, next != null);
502999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("position of the returned view must match current position",
503999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
504999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    return next;
505999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
506999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            };
507999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
508999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
509999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String getBoundsLog() {
510999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            StringBuilder sb = new StringBuilder();
511999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
512999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
513999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("\nchildren bounds\n");
514999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int childCount = getChildCount();
515999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
516999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
517999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
518999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append("[").append("start:").append(
519999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
520999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
521999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
522999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return sb.toString();
523999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
524999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
525999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
526999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
527999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (itemAnimator == null) {
528999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return;
529999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
530999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final CountDownLatch latch = new CountDownLatch(1);
531999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final boolean running = itemAnimator.isRunning(
532999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
533999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        @Override
534999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        public void onAnimationsFinished() {
535999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            latch.countDown();
536999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
537999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
538999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            );
539999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (running) {
540999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
541999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
542999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
543999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
544999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
545999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            int childCount = getChildCount();
546999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
547999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
548999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
549999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
550999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
551999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
552999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
553999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
554999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
555999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (hidden) {
556999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    continue;
557999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
558999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int position = getPosition(child);
559999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (fullyVisible) {
560999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
561999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
562999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
563999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
564999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
565999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
566999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
567999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
568999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
569999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
570999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
571999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
572999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
573999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
574999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
575999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
576999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
577999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
578999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
579999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
580999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return visibleChildren;
581999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
582999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
583999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Rect getViewBounds(View view) {
584999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (getOrientation() == HORIZONTAL) {
585999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
586999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
587999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
588999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
589999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
590999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
591999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
592999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
593999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
594999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
595999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
596999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
597999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
598999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
599999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
600999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
601999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
60242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas            mActivityRule.runOnUiThread(new Runnable() {
603999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
604999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                public void run() {
605999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int childCount = getChildCount();
606999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    Rect layoutBounds = new Rect(0, 0,
607999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mLayoutManager.getWidth(), mLayoutManager.getHeight());
608999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    for (int i = 0; i < childCount; i++) {
609999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        View child = getChildAt(i);
610999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
611999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                .getLayoutParams();
612999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
613999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        Rect childBounds = getViewBounds(child);
614999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        if (new Rect(childBounds).intersect(layoutBounds)) {
615999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            items.put(vh.mBoundItem, childBounds);
616999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
617999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
618999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
619999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            });
620999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return items;
621999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
622999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
623999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
624999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
625999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            try {
626999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
627999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.before(recycler, state);
628999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
629999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                super.onLayoutChildren(recycler, state);
630999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
631999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.after(recycler, state);
632999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
633999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } catch (Throwable t) {
634999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                postExceptionToInstrumentation(t);
635999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
636999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch.countDown();
637999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
638ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
639ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        @Override
6401e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
6413104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik                LayoutPrefetchRegistry layoutPrefetchRegistry) {
642ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            if (prefetchLatch != null) prefetchLatch.countDown();
6433104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik            super.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry);
644ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
645999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
646999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar}
647