BaseLinearLayoutManagerTest.java revision 66826566020afc8d11f183cf3fe443ac0a022384
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
18999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.content.Context;
19999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect;
20999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log;
21999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.view.View;
224143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport android.view.ViewGroup;
23999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
24999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.lang.reflect.Field;
25999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.ArrayList;
26999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.LinkedHashMap;
27999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List;
28999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map;
29999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.CountDownLatch;
30999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.concurrent.TimeUnit;
31999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
32999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
33999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
344143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static android.view.ViewGroup.LayoutParams.FILL_PARENT;
354143554adb9b31b700b6876a251a64419e6111e2Yigit Boyarimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.*;
37999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
3866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport static java.util.concurrent.TimeUnit.SECONDS;
3966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
4066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport org.hamcrest.CoreMatchers;
4166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport org.hamcrest.MatcherAssert;
4266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
43999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
44999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final boolean DEBUG = false;
46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static final String TAG = "LinearLayoutManagerTest";
47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> createBaseVariations() {
49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> variations = new ArrayList<>();
50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
534143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    for (boolean wrap : new boolean[]{false, true}) {
544143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        variations.add(
554143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                                new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap));
564143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    }
574143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
59999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
60999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return variations;
62999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
63999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    TestAdapter mTestAdapter;
66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Object... variations)
69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Config config : base) {
73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (Object variation : variations) {
74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Config newConfig = (Config) config.clone();
75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                field.set(newConfig, variation);
76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                newConfigs.add(newConfig);
77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        return newConfigs;
80999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
81999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView = inflateWrappedRV();
84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setHasFixedSize(true);
86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                : config.mTestAdapter;
88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                config.mReverseLayout);
91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
944143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        if (config.mWrap) {
954143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mRecyclerView.setLayoutParams(
964143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    new ViewGroup.LayoutParams(
974143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                            config.mOrientation == HORIZONTAL ? WRAP_CONTENT : FILL_PARENT,
984143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                            config.mOrientation == VERTICAL ? WRAP_CONTENT : FILL_PARENT
994143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    )
1004143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            );
1014143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        if (waitForFirstLayout) {
103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            waitForFirstLayout();
104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throws Throwable {
109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (state.isPreLayout()) {
115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("pending scroll position should still be pending",
116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } else {
122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    RecyclerView.ViewHolder vh =
123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertNotNull("scroll to position should work", vh);
125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        assertEquals("scroll offset should be applied properly",
127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset +
128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                        ((RecyclerView.LayoutParams) vh.itemView
129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                                .getLayoutParams()).topMargin,
130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        };
135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(2);
136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                try {
140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mTestAdapter.addAndNotify(0, 1);
141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    } else {
144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                scrollOffset);
146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                } catch (Throwable throwable) {
149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    throwable.printStackTrace();
150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        checkForMainThreadException();
156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    protected void waitForFirstLayout() throws Throwable {
159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.expectLayouts(1);
160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        setRecyclerView(mRecyclerView);
161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        mLayoutManager.waitForLayout(2);
162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        runTestOnUiThread(new Runnable() {
166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            @Override
167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            public void run() {
168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        });
171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Map<Item, Rect> after, boolean strictItemEquality) {
175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Throwable throwable = null;
176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        try {
177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertRectSetsEqual("NOT " + message, before, after, strictItemEquality);
178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        } catch (Throwable t) {
179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            throwable = t;
180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertRectSetsEqual(message, before, after, true);
186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            boolean strictItemEquality) {
190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        StringBuilder sb = new StringBuilder();
191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("checking rectangle equality.\n");
192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("before:\n");
193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        sb.append("after:\n");
197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        message = message + "\n" + sb.toString();
201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                , after.size());
203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Item beforeItem = entry.getKey();
205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            Rect afterRect = null;
206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (strictItemEquality) {
207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                afterRect = after.get(beforeItem);
208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nSame item should be visible after simple re-layout",
209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final Item afterItem = afterEntry.getKey();
213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect = afterEntry.getValue();
215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        break;
216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                assertNotNull(message + ":\nItem with same adapter index should be visible " +
219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                "after simple re-layout",
220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        afterRect);
221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    entry.getValue(), afterRect);
224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class VisibleChildren {
228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
235999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
236999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
237999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
238999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
239999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return "VisibleChildren{" +
240999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
241999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
242999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
243999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
244999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    '}';
245999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
246999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
247999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
248999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class OnLayoutListener {
249999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
250999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
251999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
252999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
253999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
254999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
255999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
256999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
257999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    static class Config implements Cloneable {
258999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
259999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        static final int DEFAULT_ITEM_COUNT = 100;
260999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
261999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mStackFromEnd;
262999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
263999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mOrientation = VERTICAL;
264999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
265999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mReverseLayout = false;
266999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
267999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
268999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
269999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
270999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
2714143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        boolean mWrap = false;
2724143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
273999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        TestAdapter mTestAdapter;
274999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
275999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
276999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
277999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
278999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromEnd;
279999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
280999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
281999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config() {
282999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
283999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
284999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
285999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config adapter(TestAdapter adapter) {
286999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mTestAdapter = adapter;
287999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
288999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
289999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
290999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
291999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
292999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
293999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
294999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
295999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config orientation(int orientation) {
296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mOrientation = orientation;
297999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
299999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
300999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
301999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mStackFromEnd = stackFromBottom;
302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mReverseLayout = reverseLayout;
307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Config itemCount(int itemCount) {
311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mItemCount = itemCount;
312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return this;
313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        // required by convention
316999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
317999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public Object clone() throws CloneNotSupportedException {
318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return super.clone();
319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String toString() {
323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return "Config{" +
324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    "mStackFromEnd=" + mStackFromEnd +
325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mOrientation=" + mOrientation +
326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mReverseLayout=" + mReverseLayout +
327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    ", mItemCount=" + mItemCount +
3294143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    ", wrap=" + mWrap +
330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    '}';
331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
3324143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
3334143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        public Config wrap(boolean wrap) {
3344143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mWrap = wrap;
3354143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            return this;
3364143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
337999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
338999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
339999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
340999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
341999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        CountDownLatch layoutLatch;
342999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
343999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OrientationHelper mSecondaryOrientation;
344999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
345999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        OnLayoutListener mOnLayoutListener;
346999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
347999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
348999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super(context, orientation, reverseLayout);
349999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
350999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
351999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void expectLayouts(int count) {
352999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch = new CountDownLatch(count);
353999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
354999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
35566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        public void waitForLayout(int seconds) throws Throwable {
35666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
35766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            checkForMainThreadException();
35866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            MatcherAssert.assertThat("all layouts should complete on time",
35966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    layoutLatch.getCount(), CoreMatchers.is(0L));
36066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            // use a runnable to ensure RV layout is finished
36166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            getInstrumentation().runOnMainSync(new Runnable() {
36266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                @Override
36366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                public void run() {
36466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                }
36566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            });
366999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
367999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
368999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
369999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void setOrientation(int orientation) {
370999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.setOrientation(orientation);
371999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            mSecondaryOrientation = null;
372999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
373999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
374999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
375999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
376999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
377999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
378999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
379999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleView(child, recycler);
380999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
381999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
382999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
383999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
384999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (DEBUG) {
385999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                Log.d(TAG,
386999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
387999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
388999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.removeAndRecycleViewAt(index, recycler);
389999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
390999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
391999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
392999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        void ensureLayoutState() {
393999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            super.ensureLayoutState();
394999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (mSecondaryOrientation == null) {
395999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
396999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        1 - getOrientation());
397999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
398999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
399999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
400999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
401999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        LayoutState createLayoutState() {
402999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return new LayoutState() {
403999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
404999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View next(RecyclerView.Recycler recycler) {
405999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final boolean hadMore = hasMore(mRecyclerView.mState);
406999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int position = mCurrentPosition;
407999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    View next = super.next(recycler);
408999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("if has more, should return a view", hadMore, next != null);
409999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    assertEquals("position of the returned view must match current position",
410999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
411999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    return next;
412999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
413999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            };
414999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
415999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
416999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public String getBoundsLog() {
417999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            StringBuilder sb = new StringBuilder();
418999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
419999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
420999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            sb.append("\nchildren bounds\n");
421999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int childCount = getChildCount();
422999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
423999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
424999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
425999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append("[").append("start:").append(
426999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
427999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
428999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
429999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return sb.toString();
430999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
431999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
432999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
433999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
434999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (itemAnimator == null) {
435999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return;
436999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
437999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final CountDownLatch latch = new CountDownLatch(1);
438999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final boolean running = itemAnimator.isRunning(
439999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
440999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        @Override
441999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        public void onAnimationsFinished() {
442999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            latch.countDown();
443999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
444999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
445999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            );
446999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (running) {
447999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
448999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
449999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
450999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
451999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
452999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            int childCount = getChildCount();
453999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
454999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
455999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
456999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            for (int i = 0; i < childCount; i++) {
457999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                View child = getChildAt(i);
458999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
459999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
460999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
461999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
462999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (hidden) {
463999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    continue;
464999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
465999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                final int position = getPosition(child);
466999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (fullyVisible) {
467999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
468999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
469999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
470999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
471999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
472999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
473999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
474999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
475999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
476999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
477999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
478999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
479999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
480999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
481999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
482999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
483999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
484999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
485999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
486999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
487999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return visibleChildren;
488999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
489999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
490999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Rect getViewBounds(View view) {
491999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            if (getOrientation() == HORIZONTAL) {
492999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
493999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
494999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
495999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
496999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
497999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } else {
498999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                return new Rect(
499999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
500999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedStart(view),
501999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
502999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
503999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
504999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
505999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
506999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
507999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
508999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
509999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            runTestOnUiThread(new Runnable() {
510999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                @Override
511999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                public void run() {
512999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    final int childCount = getChildCount();
513999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    Rect layoutBounds = new Rect(0, 0,
514999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            mLayoutManager.getWidth(), mLayoutManager.getHeight());
515999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    for (int i = 0; i < childCount; i++) {
516999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        View child = getChildAt(i);
517999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
518999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                                .getLayoutParams();
519999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
520999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        Rect childBounds = getViewBounds(child);
521999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        if (new Rect(childBounds).intersect(layoutBounds)) {
522999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                            items.put(vh.mBoundItem, childBounds);
523999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                        }
524999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    }
525999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
526999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            });
527999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            return items;
528999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
529999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
530999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        @Override
531999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
532999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            try {
533999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
534999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.before(recycler, state);
535999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
536999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                super.onLayoutChildren(recycler, state);
537999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                if (mOnLayoutListener != null) {
538999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                    mOnLayoutListener.after(recycler, state);
539999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                }
540999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            } catch (Throwable t) {
541999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar                postExceptionToInstrumentation(t);
542999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            }
543999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar            layoutLatch.countDown();
544999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar        }
545999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
546999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar
547999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar    }
548999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar}
549