BaseLinearLayoutManagerTest.java revision ec4b5429886bfe93707a85823cf40de107bd9dc6
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;
24e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertNotNull;
25e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
26e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static java.util.concurrent.TimeUnit.SECONDS;
27e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
28999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.content.Context;
29999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect;
30c587f7dba5a337169e854e235da59f595255d6ccAga Madurskaimport android.support.v4.util.Pair;
31999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log;
32999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.view.View;
334143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport android.view.ViewGroup;
34999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
35e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.CoreMatchers;
36e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.hamcrest.MatcherAssert;
37e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
38999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.lang.reflect.Field;
39999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.ArrayList;
40999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.LinkedHashMap;
41999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List;
42999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map;
43999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.CountDownLatch;
44999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.TimeUnit;
45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final boolean DEBUG = false;
49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final String TAG = "LinearLayoutManagerTest";
50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> createBaseVariations() {
52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> variations = new ArrayList<>();
53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
564143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    for (boolean wrap : new boolean[]{false, true}) {
574143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        variations.add(
584143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                                new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap));
594143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    }
604143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
62999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
63999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return variations;
65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    TestAdapter mTestAdapter;
69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Object... variations)
72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Config config : base) {
76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (Object variation : variations) {
77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Config newConfig = (Config) config.clone();
78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                field.set(newConfig, variation);
79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                newConfigs.add(newConfig);
80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return newConfigs;
83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView = inflateWrappedRV();
87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setHasFixedSize(true);
89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                : config.mTestAdapter;
91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                config.mReverseLayout);
94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
974143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        if (config.mWrap) {
984143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mRecyclerView.setLayoutParams(
994143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    new ViewGroup.LayoutParams(
100e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                            config.mOrientation == HORIZONTAL ? WRAP_CONTENT : MATCH_PARENT,
101e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                            config.mOrientation == VERTICAL ? WRAP_CONTENT : MATCH_PARENT
1024143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    )
1034143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            );
1044143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (waitForFirstLayout) {
106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            waitForFirstLayout();
107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws Throwable {
112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (state.isPreLayout()) {
118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("pending scroll position should still be pending",
119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } else {
125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    RecyclerView.ViewHolder vh =
126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertNotNull("scroll to position should work", vh);
128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("scroll offset should be applied properly",
130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset +
131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                        ((RecyclerView.LayoutParams) vh.itemView
132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                                .getLayoutParams()).topMargin,
133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        };
138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(2);
139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                try {
143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mTestAdapter.addAndNotify(0, 1);
144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    } else {
147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset);
149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } catch (Throwable throwable) {
152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    throwable.printStackTrace();
153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        checkForMainThreadException();
159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected void waitForFirstLayout() throws Throwable {
162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(1);
163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setRecyclerView(mRecyclerView);
164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Map<Item, Rect> after, boolean strictItemEquality) {
178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Throwable throwable = null;
179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        try {
180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertRectSetsEqual("NOT " + message, before, after, strictItemEquality);
181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        } catch (Throwable t) {
182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throwable = t;
183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertRectSetsEqual(message, before, after, true);
189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            boolean strictItemEquality) {
193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        StringBuilder sb = new StringBuilder();
194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("checking rectangle equality.\n");
195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("before:\n");
196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("after:\n");
200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        message = message + "\n" + sb.toString();
204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                , after.size());
206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Item beforeItem = entry.getKey();
208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Rect afterRect = null;
209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (strictItemEquality) {
210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                afterRect = after.get(beforeItem);
211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nSame item should be visible after simple re-layout",
212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final Item afterItem = afterEntry.getKey();
216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect = afterEntry.getValue();
218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        break;
219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nItem with same adapter index should be visible " +
222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                "after simple re-layout",
223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    entry.getValue(), afterRect);
227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class VisibleChildren {
231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
235999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
236999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
237999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
238999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
239999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
240999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
241999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
242999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return "VisibleChildren{" +
243999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
244999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
245999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
246999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
247999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    '}';
248999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
249999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
250999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
251999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class OnLayoutListener {
252999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
253999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
254999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
255999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
256999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
257999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
258999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
259999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
260999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class Config implements Cloneable {
261999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
262c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        static final int DEFAULT_ITEM_COUNT = 250;
263999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
264999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mStackFromEnd;
265999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
266999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mOrientation = VERTICAL;
267999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
268999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mReverseLayout = false;
269999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
270999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
271999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
272999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
273999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
2744143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        boolean mWrap = false;
2754143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
276999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        TestAdapter mTestAdapter;
277999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
278999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
279999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
280999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
281999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromEnd;
282999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
283999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
284999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config() {
285999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
286999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
287999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
288999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config adapter(TestAdapter adapter) {
289999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mTestAdapter = adapter;
290999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
291999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
292999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
293999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
294999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
295999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config orientation(int orientation) {
299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromBottom;
305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config itemCount(int itemCount) {
314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mItemCount = itemCount;
315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // required by convention
319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Object clone() throws CloneNotSupportedException {
321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return super.clone();
322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return "Config{" +
327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    "mStackFromEnd=" + mStackFromEnd +
328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mOrientation=" + mOrientation +
329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mReverseLayout=" + mReverseLayout +
330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mItemCount=" + mItemCount +
3324143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    ", wrap=" + mWrap +
333999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    '}';
334999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
3354143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
3364143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        public Config wrap(boolean wrap) {
3374143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mWrap = wrap;
3384143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            return this;
3394143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
340999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
341999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
342999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
343999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
344999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        CountDownLatch layoutLatch;
345999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
346c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        CountDownLatch snapLatch;
347c587f7dba5a337169e854e235da59f595255d6ccAga Madurska
348ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        CountDownLatch prefetchLatch;
349ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
350999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OrientationHelper mSecondaryOrientation;
351999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
352999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OnLayoutListener mOnLayoutListener;
353999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
354999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
355999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super(context, orientation, reverseLayout);
356999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
357999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
358999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void expectLayouts(int count) {
359999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch = new CountDownLatch(count);
360999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
361999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
36266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        public void waitForLayout(int seconds) throws Throwable {
36366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
36466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            checkForMainThreadException();
36566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            MatcherAssert.assertThat("all layouts should complete on time",
36666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    layoutLatch.getCount(), CoreMatchers.is(0L));
36766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            // use a runnable to ensure RV layout is finished
36866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            getInstrumentation().runOnMainSync(new Runnable() {
36966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                @Override
37066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                public void run() {
37166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                }
37266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            });
373999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
374999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
375ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        public void expectPrefetch(int count) {
376ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            prefetchLatch = new CountDownLatch(count);
377ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
378ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
379ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        public void waitForPrefetch(int seconds) throws Throwable {
380ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            prefetchLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
381ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            checkForMainThreadException();
382ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            MatcherAssert.assertThat("all prefetches should complete on time",
383ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                    prefetchLatch.getCount(), CoreMatchers.is(0L));
384ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            // use a runnable to ensure RV layout is finished
385ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            getInstrumentation().runOnMainSync(new Runnable() {
386ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                @Override
387ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                public void run() {
388ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik                }
389ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            });
390ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
391ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
392c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        public void expectIdleState(int count) {
393c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            snapLatch = new CountDownLatch(count);
394c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
395c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                @Override
396c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
397c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    super.onScrollStateChanged(recyclerView, newState);
398c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
399c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        snapLatch.countDown();
400c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        if (snapLatch.getCount() == 0L) {
401c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                            mRecyclerView.removeOnScrollListener(this);
402c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                        }
403c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    }
404c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                }
405c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            });
406c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        }
407c587f7dba5a337169e854e235da59f595255d6ccAga Madurska
408c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        public void waitForSnap(int seconds) throws Throwable {
409c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            snapLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
410c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            checkForMainThreadException();
411c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            MatcherAssert.assertThat("all scrolling should complete on time",
412c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                    snapLatch.getCount(), CoreMatchers.is(0L));
413c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            // use a runnable to ensure RV layout is finished
414c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            getInstrumentation().runOnMainSync(new Runnable() {
415c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                @Override
416c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                public void run() {
417c587f7dba5a337169e854e235da59f595255d6ccAga Madurska                }
418c587f7dba5a337169e854e235da59f595255d6ccAga Madurska            });
419c587f7dba5a337169e854e235da59f595255d6ccAga Madurska        }
420c587f7dba5a337169e854e235da59f595255d6ccAga Madurska
421999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
422999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void setOrientation(int orientation) {
423999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.setOrientation(orientation);
424999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mSecondaryOrientation = null;
425999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
426999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
427999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
428999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
429999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
430999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
431999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
432999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleView(child, recycler);
433999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
434999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
435999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
436999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
437999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
438999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG,
439999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
440999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
441999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleViewAt(index, recycler);
442999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
443999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
444999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
445999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void ensureLayoutState() {
446999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.ensureLayoutState();
447999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (mSecondaryOrientation == null) {
448999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
449999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        1 - getOrientation());
450999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
451999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
452999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
453999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
454999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        LayoutState createLayoutState() {
455999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return new LayoutState() {
456999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
457999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View next(RecyclerView.Recycler recycler) {
458999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final boolean hadMore = hasMore(mRecyclerView.mState);
459999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int position = mCurrentPosition;
460999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    View next = super.next(recycler);
461999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("if has more, should return a view", hadMore, next != null);
462999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("position of the returned view must match current position",
463999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
464999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    return next;
465999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
466999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            };
467999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
468999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
469999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String getBoundsLog() {
470999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            StringBuilder sb = new StringBuilder();
471999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
472999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
473999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("\nchildren bounds\n");
474999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int childCount = getChildCount();
475999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
476999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
477999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
478999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append("[").append("start:").append(
479999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
480999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
481999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
482999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return sb.toString();
483999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
484999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
485999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
486999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
487999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (itemAnimator == null) {
488999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return;
489999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
490999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final CountDownLatch latch = new CountDownLatch(1);
491999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final boolean running = itemAnimator.isRunning(
492999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
493999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        @Override
494999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        public void onAnimationsFinished() {
495999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            latch.countDown();
496999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
497999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
498999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            );
499999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (running) {
500999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
501999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
502999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
503999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
504999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
505999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            int childCount = getChildCount();
506999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
507999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
508999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
509999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
510999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
511999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
512999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
513999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
514999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
515999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (hidden) {
516999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    continue;
517999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
518999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int position = getPosition(child);
519999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (fullyVisible) {
520999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
521999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
522999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
523999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
524999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
525999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
526999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
527999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
528999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
529999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
530999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
531999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
532999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
533999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
534999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
535999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
536999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
537999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
538999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
539999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
540999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return visibleChildren;
541999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
542999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
543999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Rect getViewBounds(View view) {
544999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (getOrientation() == HORIZONTAL) {
545999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
546999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
547999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
548999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
549999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
550999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
551999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
552999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
553999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
554999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
555999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
556999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
557999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
558999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
559999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
560999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
561999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
562999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            runTestOnUiThread(new Runnable() {
563999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
564999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                public void run() {
565999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int childCount = getChildCount();
566999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    Rect layoutBounds = new Rect(0, 0,
567999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mLayoutManager.getWidth(), mLayoutManager.getHeight());
568999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    for (int i = 0; i < childCount; i++) {
569999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        View child = getChildAt(i);
570999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
571999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                .getLayoutParams();
572999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
573999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        Rect childBounds = getViewBounds(child);
574999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        if (new Rect(childBounds).intersect(layoutBounds)) {
575999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            items.put(vh.mBoundItem, childBounds);
576999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
577999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
578999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
579999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            });
580999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return items;
581999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
582999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
583999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
584999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
585999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            try {
586999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
587999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.before(recycler, state);
588999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
589999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                super.onLayoutChildren(recycler, state);
590999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
591999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.after(recycler, state);
592999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
593999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } catch (Throwable t) {
594999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                postExceptionToInstrumentation(t);
595999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
596999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch.countDown();
597999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
598ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik
599ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        @Override
600ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
601ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            if (prefetchLatch != null) prefetchLatch.countDown();
602ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik            return super.gatherPrefetchIndices(dx, dy, state, outIndices);
603ec4b5429886bfe93707a85823cf40de107bd9dc6Chris Craik        }
604999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
605999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar}
606