LinearLayoutManagerTest.java revision 115ba0c7b2a14aa4cd0273952195e1d8f6468f87
1d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar/*
2d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Copyright (C) 2014 The Android Open Source Project
3d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
4d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * you may not use this file except in compliance with the License.
6d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * You may obtain a copy of the License at
7d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
8d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
10d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Unless required by applicable law or agreed to in writing, software
11d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * See the License for the specific language governing permissions and
14d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * limitations under the License.
15d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar */
16d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
17d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpackage android.support.v7.widget;
18d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
19d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.content.Context;
208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.graphics.Rect;
218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.os.Parcel;
228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.os.Parcelable;
23a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.AccessibilityDelegateCompat;
24a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.accessibility.AccessibilityEventCompat;
25a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.support.v4.view.accessibility.AccessibilityRecordCompat;
268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log;
27d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View;
28504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup;
29a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent;
30310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
31d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
32c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END;
33c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START;
34310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
35310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.lang.reflect.Field;
37d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList;
388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.LinkedHashMap;
39d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List;
408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.Map;
418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.UUID;
42d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.CountDownLatch;
43d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.TimeUnit;
44504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
45d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/**
478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}.
488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p>
498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation
508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling
518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing.
528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */
53d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
54d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final boolean DEBUG = false;
568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final String TAG = "LinearLayoutManagerTest";
588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
59d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
60d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
61d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    TestAdapter mTestAdapter;
62d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
63d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    final List<Config> mBaseVariations = new ArrayList<Config>();
64d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
65d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    @Override
66d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    protected void setUp() throws Exception {
67d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        super.setUp();
68310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
69d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
70d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
71d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
72d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
73d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
74d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
75d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
76d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    protected List<Config> addConfigVariation(List<Config> base, String fieldName,
788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Object... variations)
798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Config config : base) {
838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (Object variation : variations) {
848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Config newConfig = (Config) config.clone();
858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                field.set(newConfig, variation);
868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                newConfigs.add(newConfig);
878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        return newConfigs;
908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView = new RecyclerView(getActivity());
948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setHasFixedSize(true);
95504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
96504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                : config.mTestAdapter;
978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
98d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
99d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                config.mReverseLayout);
100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
10149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
1028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
1038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForFirstLayout) {
1048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
1058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
1068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
108310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    public void testKeepFocusOnRelayout() throws Throwable {
109310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int center = (mLayoutManager.findLastVisibleItemPosition()
111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                - mLayoutManager.findFirstVisibleItemPosition()) / 2;
112115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
114310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                vh.itemView.requestFocus();
118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertTrue("view should have the focus", vh.itemView.hasFocus());
121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        // add a bunch of items right before that view, make sure it keeps its position
122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.expectLayouts(2);
123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int childCountToAdd = mRecyclerView.getChildCount() * 2;
124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mTestAdapter.addAndNotify(center, childCountToAdd);
125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        center += childCountToAdd; // offset item
126310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForLayout(2);
127310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForAnimationsToEnd(20);
128115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
129310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertNotNull("focused child should stay in layout", postVH);
130310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertSame("same view holder should be kept for unchanged child", vh, postVH);
131310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertEquals("focused child's screen position should stay unchanged", top,
132310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
133310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
134310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
135245b9720dad47a694d16a1d0f48ad462bc27989fYigit Boyar    public void testResize() throws Throwable {
136310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
137310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                , Config.DEFAULT_ITEM_COUNT)) {
138310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            stackFromEndTest(config);
139310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            removeRecyclerView();
140310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
141310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
142310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
143c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public void testScrollToPositionWithOffset() throws Throwable {
144c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        for (Config config : mBaseVariations) {
145c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffsetTest(config.itemCount(300));
146c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            removeRecyclerView();
147c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
148c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
149c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
150c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
151c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        setupByConfig(config, true);
152c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        OrientationHelper orientationHelper = OrientationHelper
153c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                .createOrientationHelper(mLayoutManager, config.mOrientation);
154c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        Rect layoutBounds = getDecoratedRecyclerViewBounds();
155c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try scrolling towards head, should not affect anything
156c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
157c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (config.mStackFromEnd) {
158c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
159c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    mLayoutManager.mOrientationHelper.getEnd() - 500);
160c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        } else {
161c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(0, 20);
162c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
163c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
164c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                before, mLayoutManager.collectChildCoordinates());
165c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try offsetting some visible children
166c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int testCount = 10;
167c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        while (testCount-- > 0) {
168c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            // get middle child
169c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
170115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            final int position = mRecyclerView.getChildLayoutPosition(child);
171c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int startOffset = config.mReverseLayout ?
172c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
173c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getDecoratedEnd(child)
174c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
175c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getStartAfterPadding();
176c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
177c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : startOffset / 2;
178c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.expectLayouts(1);
179c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(position, scrollOffset);
180c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.waitForLayout(2);
181c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int finalOffset = config.mReverseLayout ?
182c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
183c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getDecoratedEnd(child)
184c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
185c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getStartAfterPadding();
186c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            assertEquals(config + " scroll with offset on a visible child should work fine " +
187c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
188c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            + "child " + position,
189c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    scrollOffset, finalOffset);
190c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
191c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
192c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try scrolling to invisible children
193c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        testCount = 10;
194c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // we test above and below, one by one
195c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int offsetMultiplier = -1;
196c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        while (testCount-- > 0) {
197c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final TargetTuple target = findInvisibleTarget(config);
198c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final String logPrefix = config + " " + target;
199c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.expectLayouts(1);
200c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int offset = offsetMultiplier
201c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
202c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(target.mPosition, offset);
203c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.waitForLayout(2);
204c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final View child = mLayoutManager.findViewByPosition(target.mPosition);
205c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
206c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    + " should layout it", child);
207c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final Rect bounds = mLayoutManager.getViewBounds(child);
208c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (DEBUG) {
209c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
210c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        + layoutBounds + " with offset " + offset);
211c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
212c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
213c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (config.mReverseLayout) {
214c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
215c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + "layout, its end should align with recycler view's end - offset",
216c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getEndAfterPadding() - offset,
217c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getDecoratedEnd(child)
218c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                );
219c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            } else {
220c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal"
221c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + " layout its start should align with recycler view's start + "
222c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + "offset",
223c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getStartAfterPadding() + offset,
224c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getDecoratedStart(child)
225c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                );
226c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
227c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            offsetMultiplier *= -1;
228c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
229c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
230c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
231c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    private TargetTuple findInvisibleTarget(Config config) {
232c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
233c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
234c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            View child = mLayoutManager.getChildAt(i);
235115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            int position = mRecyclerView.getChildLayoutPosition(child);
236c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (position < minPosition) {
237c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                minPosition = position;
238c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
239c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (position > maxPosition) {
240c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                maxPosition = position;
241c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
242c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
243c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int tailTarget = maxPosition +
244c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
245c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int headTarget = minPosition / 2;
246c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int target;
247c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // where will the child come from ?
248c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int itemLayoutDirection;
249c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
250c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            target = tailTarget;
251c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
252c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        } else {
253c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            target = headTarget;
254c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
255c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
256c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (DEBUG) {
257c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            Log.d(TAG,
258c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
259c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
260c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new TargetTuple(target, itemLayoutDirection);
261c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
262c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
263310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    public void stackFromEndTest(final Config config) throws Throwable {
264310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final FrameLayout container = getRecyclerViewContainer();
265310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
266310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
267310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
268310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                container.setPadding(0, 0, 0, 0);
269310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
270310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
271310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
272310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(config, true);
273310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
274310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
275310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition();
276310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
277310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.expectLayouts(1);
278310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        // resize the recycler view to half
279310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
280310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
281310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
282310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                if (config.mOrientation == HORIZONTAL) {
283310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    container.setPadding(0, 0, container.getWidth() / 2, 0);
284310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                } else {
285310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    container.setPadding(0, 0, 0, container.getWidth() / 2);
286310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                }
287310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
288310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
289310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForLayout(1);
290310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        if (config.mStackFromEnd) {
291310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last visible position should not change.",
292310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
293310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last completely visible position should not change",
294310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    lastCompletelyVisibleItemPosition,
295310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    mLayoutManager.findLastCompletelyVisibleItemPosition());
296310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        } else {
297310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: first visible position should not change.",
298310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
299310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last completely visible position should not change",
300310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    firstCompletelyVisibleItemPosition,
301310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    mLayoutManager.findFirstCompletelyVisibleItemPosition());
302310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
303310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
304310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
3056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    public void testScrollToPositionWithPredictive() throws Throwable {
3066e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
3076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
3086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(3, 20);
3096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
3106e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
3116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                LinearLayoutManager.INVALID_OFFSET);
3126e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
3136e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
3146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
3156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
3166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
3176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throws Throwable {
318310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
3196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
3206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
3216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
3226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
3236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (state.isPreLayout()) {
3246e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertEquals("pending scroll position should still be pending",
3256e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
3266e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
3276e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
3286e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
3296e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
3306e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } else {
3316e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    RecyclerView.ViewHolder vh =
332115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
3336e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertNotNull("scroll to position should work", vh);
3346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
3356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("scroll offset should be applied properly",
3366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset +
3376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                        ((RecyclerView.LayoutParams) vh.itemView
3386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                                .getLayoutParams()).topMargin,
3396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
3406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
3416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
3426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
3436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        };
3446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.expectLayouts(2);
3456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        runTestOnUiThread(new Runnable() {
3466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
3476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            public void run() {
3486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                try {
3496e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mTestAdapter.addAndNotify(0, 1);
3506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
3516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
3526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    } else {
3536e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
3546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset);
3556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
3566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
3576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } catch (Throwable throwable) {
3586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    throwable.printStackTrace();
3596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
3606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
3616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
3626e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        });
3636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.waitForLayout(2);
3646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        checkForMainThreadException();
3656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
3666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
3678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private void waitForFirstLayout() throws Throwable {
368d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
3698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(mRecyclerView);
370d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
371d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
372d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
373504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    public void testRecycleDuringAnimations() throws Throwable {
374504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final AtomicInteger childCount = new AtomicInteger(0);
375504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final TestAdapter adapter = new TestAdapter(300) {
376504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
377504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public TestViewHolder onCreateViewHolder(ViewGroup parent,
378504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    int viewType) {
379504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final int cnt = childCount.incrementAndGet();
380504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
381504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
382504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder);
383504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
384504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return testViewHolder;
385504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
386504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
387310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(300)
388504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .adapter(adapter), true);
389504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
390504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
391504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
392504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public void putRecycledView(RecyclerView.ViewHolder scrap) {
393504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                super.putRecycledView(scrap);
394504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                int cnt = childCount.decrementAndGet();
395504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
396504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap);
397504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
398504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
399504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
400504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
401504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public RecyclerView.ViewHolder getRecycledView(int viewType) {
402504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
403504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (recycledView != null) {
404504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    final int cnt = childCount.incrementAndGet();
405504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    if (DEBUG) {
406504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView);
407504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
408504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
409504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return recycledView;
410504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
411504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
412504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500);
413504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        mRecyclerView.setRecycledViewPool(pool);
414504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
415504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
416504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now keep adding children to trigger more children being created etc.
417504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
418504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(15, 1);
419504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(15);
420504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
421504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
422504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
423504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
424504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
425504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
426504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now trigger lots of add again, followed by a scroll to position
427504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
428504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(5 + (i % 3) * 3, 1);
429504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(25);
430504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
431504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20);
432504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
433504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
434504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
435504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
436504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
437504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
438d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
439d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void testGetFirstLastChildrenTest() throws Throwable {
440d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        for (Config config : mBaseVariations) {
441d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            getFirstLastChildrenTest(config);
442d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
443d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
444d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
44549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testDontRecycleChildrenOnDetach() throws Throwable {
44649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(false), true);
44749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
44849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
44949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
45049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
45149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
45249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("No views are recycled", recyclerSize,
45349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
45449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
45549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
45649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
45749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
45849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testRecycleChildrenOnDetach() throws Throwable {
45949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(true), true);
46049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        final int childCount = mLayoutManager.getChildCount();
46149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
46249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
46349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
46449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
46549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
46649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mTestAdapter.getItemViewType(0), recyclerSize + childCount);
46749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
46849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("All children should be recycled", childCount + recyclerSize,
46949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
47049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
47149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
47249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
47349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
474d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void getFirstLastChildrenTest(final Config config) throws Throwable {
4758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, true);
476d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Runnable viewInBoundsTest = new Runnable() {
477d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
478d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
479d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
480d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final String boundsLog = mLayoutManager.getBoundsLog();
481d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nfirst visible child should match traversal result\n"
482d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstVisiblePosition,
483d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstVisibleItemPosition()
484d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
485d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
486d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nfirst fully visible child should match traversal result\n"
487d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
488d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
489d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
490d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
491d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nlast visible child should match traversal result\n"
492d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastVisiblePosition,
493d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastVisibleItemPosition()
494d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
495d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
496d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nlast fully visible child should match traversal result\n"
497d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
498d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastCompletelyVisibleItemPosition()
499d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
500d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
501d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
502d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
503d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
504d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // case
505d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
506d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
507d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
508d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
509d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.smoothScrollToPosition(scrollPosition);
510d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
511d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
512d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        while (mLayoutManager.isSmoothScrolling() ||
513d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
514d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            runTestOnUiThread(viewInBoundsTest);
5158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            Thread.sleep(400);
516d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
517d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // delete all items
518d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(2);
519d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
520d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
521d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // test empty case
522d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
523d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // set a new adapter with huge items to test full bounds check
524d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
525d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
526d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final TestAdapter newAdapter = new TestAdapter(100) {
527d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
528d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void onBindViewHolder(TestViewHolder holder,
529d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    int position) {
530d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                super.onBindViewHolder(holder, position);
531310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                if (config.mOrientation == HORIZONTAL) {
532d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumWidth(totalSpace + 5);
533d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                } else {
534d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumHeight(totalSpace + 5);
535d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
536d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
537d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
538d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
539d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
540d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
541d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.setAdapter(newAdapter);
542d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
543d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
544d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
545d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
546d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
547d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
5488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void testSavedState() throws Throwable {
5498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
5508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
5518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
5538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // do nothing
5548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
5588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "doing nothing";
5598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
5618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
5628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
5648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
5658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
5668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
5678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
5718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position";
5728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
5748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
5758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
5778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
5788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
5798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                50);
5808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
5818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
5858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with positive offset";
5868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
5888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
5898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
5918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
5928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
5938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                -50);
5948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
5958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
5968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
5988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
5998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with negative offset";
6008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
6028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
6038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
6058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
6068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
6088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Doing nothing";
6098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
6118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
6128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
6148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // update config as well so that restore assertions will work
6158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mOrientation = 1 - config.mOrientation;
6168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setOrientation(config.mOrientation);
6178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
6218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
6228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
6268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing orientation";
6278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
6298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
6308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
6328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mStackFromEnd = !config.mStackFromEnd;
6338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
6348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
6388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return true; //stack from end should not move items on change
6398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
6438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing stack from end";
6448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
6468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
6478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
6498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mReverseLayout = !config.mReverseLayout;
6508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setReverseLayout(config.mReverseLayout);
6518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
6558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
6568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
6608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing reverse layout";
6618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
66275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                },
66375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                new PostRestoreRunnable() {
66449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
66549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
66649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
66749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
66849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
66949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
67049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
67149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
67249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        return true;
67349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
67449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
67549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
67649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    String describe() {
677b97e8219784e623526bc3c6077a698d608f04fd9Yigit Boyar                        return "Change should recycle children";
67849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
67949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                },
68049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                new PostRestoreRunnable() {
68175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    int position;
68275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
68375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
68475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        position = mTestAdapter.getItemCount() / 2;
68575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        mLayoutManager.scrollToPosition(position);
68675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
68775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
68875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
68975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
69075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        return mTestAdapter.getItemCount() == 0;
69175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
69275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
69375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
69475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    String describe() {
695aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                        return "Scroll to position " + position ;
69675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
69775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
69875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
69975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterReLayout(Config config) {
70075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        if (mTestAdapter.getItemCount() > 0) {
701aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                            assertEquals(config + ":scrolled view should be last completely visible",
70275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                                    position,
703aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                    config.mStackFromEnd ?
704aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                            mLayoutManager.findLastCompletelyVisibleItemPosition()
705aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                        : mLayoutManager.findFirstCompletelyVisibleItemPosition());
70675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        }
70775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
7088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
7098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
71075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        boolean[] waitForLayoutOptions = new boolean[]{true, false};
71149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
71249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
71349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        for (Config config : variations) {
7148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
7158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                for (boolean waitForLayout : waitForLayoutOptions) {
7168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
7178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
7188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                postRestoreRunnable);
7198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        removeRecyclerView();
7208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
7238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
7248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
7258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
7268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void savedStateTest(Config config, boolean waitForLayout,
7288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
7298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
7308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (DEBUG) {
7318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
7328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    config + " post layout action " + postLayoutOperation.describe() +
7338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    "post restore action " + postRestoreOperation.describe());
7348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
7358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, false);
7368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
7378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
7388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            postLayoutOperation.run();
7398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
7408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
7418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcelable savedState = mRecyclerView.onSaveInstanceState();
7428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // we append a suffix to the parcelable to test out of bounds
7438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        String parcelSuffix = UUID.randomUUID().toString();
7448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcel parcel = Parcel.obtain();
7458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState.writeToParcel(parcel, 0);
7468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.writeString(parcelSuffix);
7478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
7488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // reset for reading
7498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.setDataPosition(0);
7508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // re-create
7518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
7528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
7538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        RecyclerView restored = new RecyclerView(getActivity());
7558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // this config should be no op.
7568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
757b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar                config.mOrientation, config.mReverseLayout);
758b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
7598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setLayoutManager(mLayoutManager);
7608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // use the same adapter for Rect matching
7618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setAdapter(mTestAdapter);
7628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.onRestoreInstanceState(savedState);
7638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        postRestoreOperation.onAfterRestore(config);
7648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
7658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                parcel.readString());
7668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.expectLayouts(1);
7678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(restored);
7688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.waitForLayout(2);
7698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // calculate prefix here instead of above to include post restore changes
7708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
7718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                "\npostRestore:" + postRestoreOperation.describe() + "\n";
7728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
7738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mReverseLayout, mLayoutManager.getReverseLayout());
7748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, orientation should be preserved",
7758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mOrientation, mLayoutManager.getOrientation());
7768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, stack from end should be preserved",
7778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mStackFromEnd, mLayoutManager.getStackFromEnd());
7788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
7798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (postRestoreOperation.shouldLayoutMatch(config)) {
7808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsEqual(
7818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix + ": on restore, previous view positions should be preserved",
7828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
7838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
7848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsNotEqual(
7858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix
78649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + ": on restore with changes, previous view positions should NOT "
78749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + "be preserved",
7888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
7898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
79075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar            postRestoreOperation.onAfterReLayout(config);
7918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
7928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
7938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
7958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
7968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
7978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
7988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
7998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
8008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
8018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
8028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
8048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Map<Item, Rect> after) {
8058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Throwable throwable = null;
8068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        try {
8078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertRectSetsEqual("NOT " + message, before, after);
8088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        } catch (Throwable t) {
8098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throwable = t;
8108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
8128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
8138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
815668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        StringBuilder sb = new StringBuilder();
816668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("checking rectangle equality.");
817668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar         sb.append("before:\n");
818668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
819668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
820668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        }
821668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("after:\n");
822668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
823668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
8248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
825668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        message = message + "\n" + sb.toString();
8268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
8278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                , after.size());
8288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
8298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Rect afterRect = after.get(entry.getKey());
8308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertNotNull(message + ":\nSame item should be visible after simple re-layout",
8318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    afterRect);
8328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
8338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    entry.getValue(), afterRect);
8348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
8368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
837a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar    public void testAccessibilityPositions() throws Throwable {
838a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
839a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityDelegateCompat delegateCompat = mRecyclerView
840a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                .getCompatAccessibilityDelegate();
841a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityEvent event = AccessibilityEvent.obtain();
842a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        runTestOnUiThread(new Runnable() {
843a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            @Override
844a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            public void run() {
845a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event);
846a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            }
847a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        });
848a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityRecordCompat record = AccessibilityEventCompat
849a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                .asRecord(event);
850a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have first position",
851a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                record.getFromIndex(),
852a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findFirstVisibleItemPosition());
853a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have last position",
854a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                record.getToIndex(),
855a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findLastVisibleItemPosition());
856a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar    }
857a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar
858d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    static class VisibleChildren {
859d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
860d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
861d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
862d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
863d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
864d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
865d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
866d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
867d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
868d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
869d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
870d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "VisibleChildren{" +
871d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
872d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
873d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
874d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
875d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
876d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
877d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
878d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
8798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostLayoutRunnable {
8808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract void run() throws Throwable;
8828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
8848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
8858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostRestoreRunnable {
8878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        void onAfterRestore(Config config) throws Throwable {
8898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
8928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        boolean shouldLayoutMatch(Config config) {
8948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return true;
8958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
89675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
89775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        void onAfterReLayout(Config config) {
89875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
89975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        };
9008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
9018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
903d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
904d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        CountDownLatch layoutLatch;
905d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
9068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        OrientationHelper mSecondaryOrientation;
9078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        OnLayoutListener mOnLayoutListener;
9096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
910d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
911d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            super(context, orientation, reverseLayout);
912d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
913d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
914d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void expectLayouts(int count) {
915d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch = new CountDownLatch(count);
916d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
917d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
918d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void waitForLayout(long timeout) throws InterruptedException {
919d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            waitForLayout(timeout, TimeUnit.SECONDS);
920d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
921d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
9228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
9238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public void setOrientation(int orientation) {
9248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            super.setOrientation(orientation);
9258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            mSecondaryOrientation = null;
9268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
9278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
929504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
930504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            if (DEBUG) {
931504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
932504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
933504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            super.removeAndRecycleView(child, recycler);
934504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
935504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
936504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
937504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
938504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            if (DEBUG) {
939504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
940504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
941504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            super.removeAndRecycleViewAt(index, recycler);
942504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
943504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
944504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
94594c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar        void ensureLayoutState() {
94694c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar            super.ensureLayoutState();
9478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mSecondaryOrientation == null) {
94894c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
94994c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                        1 - getOrientation());
9508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
9518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
9528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
953d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
9546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
955d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            assertEquals("all expected layouts should be executed at the expected time",
956d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    0, layoutLatch.getCount());
9576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            getInstrumentation().waitForIdleSync();
958d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
959d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
960d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String getBoundsLog() {
961d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            StringBuilder sb = new StringBuilder();
962d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
963d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
964d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("\nchildren bounds\n");
965d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int childCount = getChildCount();
966d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
967d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
968d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
969d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append("[").append("start:").append(
970d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
971d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
972d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
973d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return sb.toString();
974d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
975d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
976310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
977310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
978310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            if (itemAnimator == null) {
979310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                return;
980310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
981310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            final CountDownLatch latch = new CountDownLatch(1);
982310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            final boolean running = itemAnimator.isRunning(
983310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
984310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        @Override
985310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        public void onAnimationsFinished() {
986310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                            latch.countDown();
987310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        }
988310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    }
989310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            );
990310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            if (running) {
991310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
992310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
993310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
994310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
995d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
996d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            int childCount = getChildCount();
997d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
998d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
999d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
1000d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
1001d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
1002d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
1003d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
1004d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
1005d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
1006d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (hidden) {
1007d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    continue;
1008d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1009d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int position = getPosition(child);
1010d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (fullyVisible) {
1011d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
1012d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
1013d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
1014d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
1015d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1016d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
1017d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
1018d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
1019d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1020d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1021d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
1022d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
1023d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
1024d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1025d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1026d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
1027d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
1028d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1029d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1030d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
1031d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return visibleChildren;
1032d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1033d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Rect getViewBounds(View view) {
10358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (getOrientation() == HORIZONTAL) {
10368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
10378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
10388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
10398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
10408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
10418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
10428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
10438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
10448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
10458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
10468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
10478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
10488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
10508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
10528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
10538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            runTestOnUiThread(new Runnable() {
10548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                @Override
10558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                public void run() {
10568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    final int childCount = getChildCount();
10578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (int i = 0; i < childCount; i++) {
10588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        View child = getChildAt(i);
10598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
10608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                .getLayoutParams();
10618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
1062115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                        items.put(vh.mBoundItem, getViewBounds(child));
10638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
10648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
10658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            });
10668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return items;
10678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
10688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1069d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
1070d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
10716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            try {
10726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (mOnLayoutListener != null) {
10736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mOnLayoutListener.before(recycler, state);
10746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
10756e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                super.onLayoutChildren(recycler, state);
10766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (mOnLayoutListener != null) {
10776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mOnLayoutListener.after(recycler, state);
10786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
10796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            } catch (Throwable t) {
10806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                postExceptionToInstrumentation(t);
10816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
1082d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch.countDown();
1083d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
10846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
10856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
10866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
10876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
10886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    static class OnLayoutListener {
10896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
10906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
1091d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
1092d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    static class Config implements Cloneable {
1094d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private static final int DEFAULT_ITEM_COUNT = 100;
1096d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1097d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private boolean mStackFromEnd;
1098d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1099310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int mOrientation = VERTICAL;
1100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        boolean mReverseLayout = false;
1102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
110349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
110449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
1105d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
1106d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1107504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        TestAdapter mTestAdapter;
1108504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1109d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
1110d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
1111d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
1112d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromEnd;
1113d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1114d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1115d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config() {
1116d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1117d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1118d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1119504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        Config adapter(TestAdapter adapter) {
1120504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            mTestAdapter = adapter;
1121504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            return this;
1122504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
1123504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
112449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
112549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
112649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            return this;
112749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        }
112849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
1129d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config orientation(int orientation) {
1130d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
1131d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1132d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1133d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1134d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
1135d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromBottom;
1136d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1137d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1138d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1139d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
1140d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
1141d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1142d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1143d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1144d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config itemCount(int itemCount) {
1145d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mItemCount = itemCount;
1146d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1147d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1148d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
11498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // required by convention
11508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
11518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public Object clone() throws CloneNotSupportedException {
11528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return super.clone();
11538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
11548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1155d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
1156d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
1157d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "Config{" +
1158d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "mStackFromEnd=" + mStackFromEnd +
1159d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mOrientation=" + mOrientation +
1160d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mReverseLayout=" + mReverseLayout +
116149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
1162d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mItemCount=" + mItemCount +
1163d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
1164d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1165d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
1166d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar}
1167