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