LinearLayoutManagerTest.java revision 542f1260934df280985294eaef1ec8469863281f
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;
26542f1260934df280985294eaef1ec8469863281fYigit Boyarimport android.util.AttributeSet;
278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log;
28d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View;
29504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup;
30a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent;
31310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
32d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
33c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END;
34c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START;
35310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
36310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.lang.reflect.Field;
38d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList;
398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.LinkedHashMap;
40d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List;
418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.Map;
428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.UUID;
43d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.CountDownLatch;
44d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.TimeUnit;
45504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
46d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/**
488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}.
498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p>
508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation
518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling
528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing.
538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */
54d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
55d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final boolean DEBUG = false;
578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final String TAG = "LinearLayoutManagerTest";
598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
60d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
61d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
62d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    TestAdapter mTestAdapter;
63d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
64d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    final List<Config> mBaseVariations = new ArrayList<Config>();
65d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
66d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    @Override
67d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    protected void setUp() throws Exception {
68d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        super.setUp();
69310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
70d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
71d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
72d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
73d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
74d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
75d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
76d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
77d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    protected List<Config> addConfigVariation(List<Config> base, String fieldName,
798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Object... variations)
808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Config config : base) {
848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (Object variation : variations) {
858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Config newConfig = (Config) config.clone();
868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                field.set(newConfig, variation);
878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                newConfigs.add(newConfig);
888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        return newConfigs;
918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
94542f1260934df280985294eaef1ec8469863281fYigit Boyar        mRecyclerView = new WrappedRecyclerView(getActivity());
95542f1260934df280985294eaef1ec8469863281fYigit Boyar
968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setHasFixedSize(true);
97504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
98504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                : config.mTestAdapter;
998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                config.mReverseLayout);
102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
10349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
1048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
1058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForFirstLayout) {
1068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
1078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
1088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    public void testKeepFocusOnRelayout() throws Throwable {
111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
112310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int center = (mLayoutManager.findLastVisibleItemPosition()
113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                - mLayoutManager.findFirstVisibleItemPosition()) / 2;
114115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                vh.itemView.requestFocus();
120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertTrue("view should have the focus", vh.itemView.hasFocus());
123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        // add a bunch of items right before that view, make sure it keeps its position
124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.expectLayouts(2);
125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int childCountToAdd = mRecyclerView.getChildCount() * 2;
126310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mTestAdapter.addAndNotify(center, childCountToAdd);
127310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        center += childCountToAdd; // offset item
128310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForLayout(2);
129310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForAnimationsToEnd(20);
130115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
131310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertNotNull("focused child should stay in layout", postVH);
132310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertSame("same view holder should be kept for unchanged child", vh, postVH);
133310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertEquals("focused child's screen position should stay unchanged", top,
134310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
135310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
136310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
137542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepFullFocusOnResize() throws Throwable {
138542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true);
139542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
140542f1260934df280985294eaef1ec8469863281fYigit Boyar
141542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepPartialFocusOnResize() throws Throwable {
142542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false);
143542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
144542f1260934df280985294eaef1ec8469863281fYigit Boyar
145542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepReverseFullFocusOnResize() throws Throwable {
146542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true);
147542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
148542f1260934df280985294eaef1ec8469863281fYigit Boyar
149542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepReversePartialFocusOnResize() throws Throwable {
150542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false);
151542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
152542f1260934df280985294eaef1ec8469863281fYigit Boyar
153542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepStackFromEndFullFocusOnResize() throws Throwable {
154542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true);
155542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
156542f1260934df280985294eaef1ec8469863281fYigit Boyar
157542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void testKeepStackFromEndPartialFocusOnResize() throws Throwable {
158542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false);
159542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
160542f1260934df280985294eaef1ec8469863281fYigit Boyar
161542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void keepFocusOnResizeTest(final Config config, boolean fullyVisible) throws Throwable {
162542f1260934df280985294eaef1ec8469863281fYigit Boyar        setupByConfig(config, true);
163542f1260934df280985294eaef1ec8469863281fYigit Boyar        final int targetPosition;
164542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (config.mStackFromEnd) {
165542f1260934df280985294eaef1ec8469863281fYigit Boyar            targetPosition = mLayoutManager.findFirstVisibleItemPosition();
166542f1260934df280985294eaef1ec8469863281fYigit Boyar        } else {
167542f1260934df280985294eaef1ec8469863281fYigit Boyar            targetPosition = mLayoutManager.findLastVisibleItemPosition();
168542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
169542f1260934df280985294eaef1ec8469863281fYigit Boyar        final OrientationHelper helper = mLayoutManager.mOrientationHelper;
170542f1260934df280985294eaef1ec8469863281fYigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView
171542f1260934df280985294eaef1ec8469863281fYigit Boyar                .findViewHolderForLayoutPosition(targetPosition);
172542f1260934df280985294eaef1ec8469863281fYigit Boyar
173542f1260934df280985294eaef1ec8469863281fYigit Boyar        // scroll enough to offset the child
174542f1260934df280985294eaef1ec8469863281fYigit Boyar        int startMargin = helper.getDecoratedStart(vh.itemView) -
175542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getStartAfterPadding();
176542f1260934df280985294eaef1ec8469863281fYigit Boyar        int endMargin = helper.getEndAfterPadding() -
177542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedEnd(vh.itemView);
178542f1260934df280985294eaef1ec8469863281fYigit Boyar        Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin);
179542f1260934df280985294eaef1ec8469863281fYigit Boyar        requestFocus(vh.itemView);
180542f1260934df280985294eaef1ec8469863281fYigit Boyar        runTestOnUiThread(new Runnable() {
181542f1260934df280985294eaef1ec8469863281fYigit Boyar            @Override
182542f1260934df280985294eaef1ec8469863281fYigit Boyar            public void run() {
183542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("view should gain the focus", vh.itemView.hasFocus());
184542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
185542f1260934df280985294eaef1ec8469863281fYigit Boyar        });
186542f1260934df280985294eaef1ec8469863281fYigit Boyar        do {
187542f1260934df280985294eaef1ec8469863281fYigit Boyar            Thread.sleep(100);
188542f1260934df280985294eaef1ec8469863281fYigit Boyar        } while (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE);
189542f1260934df280985294eaef1ec8469863281fYigit Boyar        // scroll enough to offset the child
190542f1260934df280985294eaef1ec8469863281fYigit Boyar        startMargin = helper.getDecoratedStart(vh.itemView) -
191542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getStartAfterPadding();
192542f1260934df280985294eaef1ec8469863281fYigit Boyar        endMargin = helper.getEndAfterPadding() -
193542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedEnd(vh.itemView);
194542f1260934df280985294eaef1ec8469863281fYigit Boyar
195542f1260934df280985294eaef1ec8469863281fYigit Boyar        Log.d(TAG, "start margin " + startMargin + " , end margin:" + endMargin);
196542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("View should become fully visible", startMargin >= 0 && endMargin >= 0);
197542f1260934df280985294eaef1ec8469863281fYigit Boyar
198542f1260934df280985294eaef1ec8469863281fYigit Boyar        int expectedOffset = 0;
199542f1260934df280985294eaef1ec8469863281fYigit Boyar        boolean offsetAtStart = false;
200542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (!fullyVisible) {
201542f1260934df280985294eaef1ec8469863281fYigit Boyar            // move it a bit such that it is no more fully visible
202542f1260934df280985294eaef1ec8469863281fYigit Boyar            final int childSize = helper
203542f1260934df280985294eaef1ec8469863281fYigit Boyar                    .getDecoratedMeasurement(vh.itemView);
204542f1260934df280985294eaef1ec8469863281fYigit Boyar            expectedOffset = childSize / 3;
205542f1260934df280985294eaef1ec8469863281fYigit Boyar            if (startMargin < endMargin) {
206542f1260934df280985294eaef1ec8469863281fYigit Boyar                scrollBy(expectedOffset);
207542f1260934df280985294eaef1ec8469863281fYigit Boyar                offsetAtStart = true;
208542f1260934df280985294eaef1ec8469863281fYigit Boyar            } else {
209542f1260934df280985294eaef1ec8469863281fYigit Boyar                scrollBy(-expectedOffset);
210542f1260934df280985294eaef1ec8469863281fYigit Boyar                offsetAtStart = false;
211542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
212542f1260934df280985294eaef1ec8469863281fYigit Boyar            startMargin = helper.getDecoratedStart(vh.itemView) -
213542f1260934df280985294eaef1ec8469863281fYigit Boyar                    helper.getStartAfterPadding();
214542f1260934df280985294eaef1ec8469863281fYigit Boyar            endMargin = helper.getEndAfterPadding() -
215542f1260934df280985294eaef1ec8469863281fYigit Boyar                    helper.getDecoratedEnd(vh.itemView);
216542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("test sanity, view should not be fully visible", startMargin < 0
217542f1260934df280985294eaef1ec8469863281fYigit Boyar                    || endMargin < 0);
218542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
219542f1260934df280985294eaef1ec8469863281fYigit Boyar
220542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.expectLayouts(1);
221542f1260934df280985294eaef1ec8469863281fYigit Boyar        runTestOnUiThread(new Runnable() {
222542f1260934df280985294eaef1ec8469863281fYigit Boyar            @Override
223542f1260934df280985294eaef1ec8469863281fYigit Boyar            public void run() {
224542f1260934df280985294eaef1ec8469863281fYigit Boyar                final ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams();
225542f1260934df280985294eaef1ec8469863281fYigit Boyar                if (config.mOrientation == HORIZONTAL) {
226542f1260934df280985294eaef1ec8469863281fYigit Boyar                    layoutParams.width = mRecyclerView.getWidth() / 2;
227542f1260934df280985294eaef1ec8469863281fYigit Boyar                } else {
228542f1260934df280985294eaef1ec8469863281fYigit Boyar                    layoutParams.height = mRecyclerView.getHeight() / 2;
229542f1260934df280985294eaef1ec8469863281fYigit Boyar                }
230542f1260934df280985294eaef1ec8469863281fYigit Boyar                mRecyclerView.setLayoutParams(layoutParams);
231542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
232542f1260934df280985294eaef1ec8469863281fYigit Boyar        });
233542f1260934df280985294eaef1ec8469863281fYigit Boyar        Thread.sleep(100);
234542f1260934df280985294eaef1ec8469863281fYigit Boyar        // add a bunch of items right before that view, make sure it keeps its position
235542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.waitForLayout(2);
236542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.waitForAnimationsToEnd(20);
237542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("view should preserve the focus", vh.itemView.hasFocus());
238542f1260934df280985294eaef1ec8469863281fYigit Boyar        final RecyclerView.ViewHolder postVH = mRecyclerView
239542f1260934df280985294eaef1ec8469863281fYigit Boyar                .findViewHolderForLayoutPosition(targetPosition);
240542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertNotNull("focused child should stay in layout", postVH);
241542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertSame("same view holder should be kept for unchanged child", vh, postVH);
242542f1260934df280985294eaef1ec8469863281fYigit Boyar        View focused = postVH.itemView;
243542f1260934df280985294eaef1ec8469863281fYigit Boyar
244542f1260934df280985294eaef1ec8469863281fYigit Boyar        startMargin = helper.getDecoratedStart(focused) - helper.getStartAfterPadding();
245542f1260934df280985294eaef1ec8469863281fYigit Boyar        endMargin = helper.getEndAfterPadding() - helper.getDecoratedEnd(focused);
246542f1260934df280985294eaef1ec8469863281fYigit Boyar
247542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("focused child should be somewhat visible",
248542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedStart(focused) < helper.getEndAfterPadding()
249542f1260934df280985294eaef1ec8469863281fYigit Boyar                        && helper.getDecoratedEnd(focused) > helper.getStartAfterPadding());
250542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (fullyVisible) {
251542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("focused child end should stay fully visible",
252542f1260934df280985294eaef1ec8469863281fYigit Boyar                    endMargin >= 0);
253542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("focused child start should stay fully visible",
254542f1260934df280985294eaef1ec8469863281fYigit Boyar                    startMargin >= 0);
255542f1260934df280985294eaef1ec8469863281fYigit Boyar        } else {
256542f1260934df280985294eaef1ec8469863281fYigit Boyar            if (offsetAtStart) {
257542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("start should preserve its offset", startMargin < 0);
258542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("end should be visible", endMargin >= 0);
259542f1260934df280985294eaef1ec8469863281fYigit Boyar            } else {
260542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("end should preserve its offset", endMargin < 0);
261542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("start should be visible", startMargin >= 0);
262542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
263542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
264542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
265542f1260934df280985294eaef1ec8469863281fYigit Boyar
266245b9720dad47a694d16a1d0f48ad462bc27989fYigit Boyar    public void testResize() throws Throwable {
267310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
268310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                , Config.DEFAULT_ITEM_COUNT)) {
269310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            stackFromEndTest(config);
270310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            removeRecyclerView();
271310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
272310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
273310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
274c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public void testScrollToPositionWithOffset() throws Throwable {
275c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        for (Config config : mBaseVariations) {
276c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffsetTest(config.itemCount(300));
277c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            removeRecyclerView();
278c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
279c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
280c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
281c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
282c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        setupByConfig(config, true);
283c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        OrientationHelper orientationHelper = OrientationHelper
284c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                .createOrientationHelper(mLayoutManager, config.mOrientation);
285c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        Rect layoutBounds = getDecoratedRecyclerViewBounds();
286c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try scrolling towards head, should not affect anything
287c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
288c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (config.mStackFromEnd) {
289c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
290c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    mLayoutManager.mOrientationHelper.getEnd() - 500);
291c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        } else {
292c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(0, 20);
293c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
294c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
295c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                before, mLayoutManager.collectChildCoordinates());
296c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try offsetting some visible children
297c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int testCount = 10;
298c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        while (testCount-- > 0) {
299c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            // get middle child
300c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
301115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            final int position = mRecyclerView.getChildLayoutPosition(child);
302c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int startOffset = config.mReverseLayout ?
303c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
304c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getDecoratedEnd(child)
305c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
306c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getStartAfterPadding();
307c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
308c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : startOffset / 2;
309c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.expectLayouts(1);
310c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(position, scrollOffset);
311c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.waitForLayout(2);
312c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int finalOffset = config.mReverseLayout ?
313c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    orientationHelper.getEndAfterPadding() - orientationHelper
314c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getDecoratedEnd(child)
315c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    : orientationHelper.getDecoratedStart(child) - orientationHelper
316c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            .getStartAfterPadding();
317c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            assertEquals(config + " scroll with offset on a visible child should work fine " +
318c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
319c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                            + "child " + position,
320c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    scrollOffset, finalOffset);
321c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
322c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
323c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // try scrolling to invisible children
324c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        testCount = 10;
325c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // we test above and below, one by one
326c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int offsetMultiplier = -1;
327c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        while (testCount-- > 0) {
328c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final TargetTuple target = findInvisibleTarget(config);
329c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final String logPrefix = config + " " + target;
330c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.expectLayouts(1);
331c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final int offset = offsetMultiplier
332c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
333c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            scrollToPositionWithOffset(target.mPosition, offset);
334c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            mLayoutManager.waitForLayout(2);
335c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final View child = mLayoutManager.findViewByPosition(target.mPosition);
336c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
337c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    + " should layout it", child);
338c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            final Rect bounds = mLayoutManager.getViewBounds(child);
339c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (DEBUG) {
340c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
341c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        + layoutBounds + " with offset " + offset);
342c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
343c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
344c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (config.mReverseLayout) {
345c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
346c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + "layout, its end should align with recycler view's end - offset",
347c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getEndAfterPadding() - offset,
348c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getDecoratedEnd(child)
349c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                );
350c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            } else {
351c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal"
352c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + " layout its start should align with recycler view's start + "
353c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                                + "offset",
354c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getStartAfterPadding() + offset,
355c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                        orientationHelper.getDecoratedStart(child)
356c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                );
357c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
358c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            offsetMultiplier *= -1;
359c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
360c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
361c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
362c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    private TargetTuple findInvisibleTarget(Config config) {
363c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
364c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
365c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            View child = mLayoutManager.getChildAt(i);
366115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            int position = mRecyclerView.getChildLayoutPosition(child);
367c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (position < minPosition) {
368c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                minPosition = position;
369c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
370c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            if (position > maxPosition) {
371c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                maxPosition = position;
372c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            }
373c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
374c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int tailTarget = maxPosition +
375c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
376c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int headTarget = minPosition / 2;
377c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int target;
378c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        // where will the child come from ?
379c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int itemLayoutDirection;
380c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
381c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            target = tailTarget;
382c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
383c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        } else {
384c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            target = headTarget;
385c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
386c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
387c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        if (DEBUG) {
388c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            Log.d(TAG,
389c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
390c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
391c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new TargetTuple(target, itemLayoutDirection);
392c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
393c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
394310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    public void stackFromEndTest(final Config config) throws Throwable {
395310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final FrameLayout container = getRecyclerViewContainer();
396310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
397310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
398310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
399310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                container.setPadding(0, 0, 0, 0);
400310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
401310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
402310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
403310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(config, true);
404310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
405310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
406310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition();
407310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
408310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.expectLayouts(1);
409310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        // resize the recycler view to half
410310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        runTestOnUiThread(new Runnable() {
411310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            @Override
412310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            public void run() {
413310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                if (config.mOrientation == HORIZONTAL) {
414310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    container.setPadding(0, 0, container.getWidth() / 2, 0);
415310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                } else {
416310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    container.setPadding(0, 0, 0, container.getWidth() / 2);
417310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                }
418310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
419310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        });
420310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForLayout(1);
421310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        if (config.mStackFromEnd) {
422310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last visible position should not change.",
423310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
424310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last completely visible position should not change",
425310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    lastCompletelyVisibleItemPosition,
426310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    mLayoutManager.findLastCompletelyVisibleItemPosition());
427310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        } else {
428310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: first visible position should not change.",
429310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
430310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            assertEquals("[" + config + "]: last completely visible position should not change",
431310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    firstCompletelyVisibleItemPosition,
432310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    mLayoutManager.findFirstCompletelyVisibleItemPosition());
433310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
434310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
435310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
4366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    public void testScrollToPositionWithPredictive() throws Throwable {
4376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
4386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
4396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(3, 20);
4406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
4416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
4426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                LinearLayoutManager.INVALID_OFFSET);
4436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
4446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
4456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
4466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
4476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
4486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throws Throwable {
449310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
4506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
4516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
4526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
4536e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
4546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (state.isPreLayout()) {
4556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertEquals("pending scroll position should still be pending",
4566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
4576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
4586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
4596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
4606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
4616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } else {
4626e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    RecyclerView.ViewHolder vh =
463115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
4646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertNotNull("scroll to position should work", vh);
4656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
4666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("scroll offset should be applied properly",
4676e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset +
4686e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                        ((RecyclerView.LayoutParams) vh.itemView
4696e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                                .getLayoutParams()).topMargin,
4706e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
4716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
4726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
4736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
4746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        };
4756e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.expectLayouts(2);
4766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        runTestOnUiThread(new Runnable() {
4776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
4786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            public void run() {
4796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                try {
4806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mTestAdapter.addAndNotify(0, 1);
4816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
4826e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
4836e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    } else {
4846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
4856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset);
4866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
4876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
4886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } catch (Throwable throwable) {
4896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    throwable.printStackTrace();
4906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
4916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
4926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
4936e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        });
4946e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.waitForLayout(2);
4956e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        checkForMainThreadException();
4966e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
4976e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
4988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private void waitForFirstLayout() throws Throwable {
499d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
5008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(mRecyclerView);
501d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
502d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
503d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
504504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    public void testRecycleDuringAnimations() throws Throwable {
505504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final AtomicInteger childCount = new AtomicInteger(0);
506504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final TestAdapter adapter = new TestAdapter(300) {
507504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
508504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public TestViewHolder onCreateViewHolder(ViewGroup parent,
509504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    int viewType) {
510504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final int cnt = childCount.incrementAndGet();
511504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
512504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
513504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder);
514504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
515504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return testViewHolder;
516504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
517504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
518310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(300)
519504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .adapter(adapter), true);
520504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
521504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
522504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
523504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public void putRecycledView(RecyclerView.ViewHolder scrap) {
524504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                super.putRecycledView(scrap);
525504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                int cnt = childCount.decrementAndGet();
526504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
527504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap);
528504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
529504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
530504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
531504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
532504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public RecyclerView.ViewHolder getRecycledView(int viewType) {
533504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
534504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (recycledView != null) {
535504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    final int cnt = childCount.incrementAndGet();
536504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    if (DEBUG) {
537504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView);
538504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
539504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
540504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return recycledView;
541504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
542504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
543504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500);
544504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        mRecyclerView.setRecycledViewPool(pool);
545504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
546504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
547504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now keep adding children to trigger more children being created etc.
548504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
549504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(15, 1);
550504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(15);
551504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
552504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
553504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
554504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
555504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
556504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
557504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now trigger lots of add again, followed by a scroll to position
558504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
559504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(5 + (i % 3) * 3, 1);
560504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(25);
561504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
562504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20);
563504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
564504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
565504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
566504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
567504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
568504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
569d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
570d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void testGetFirstLastChildrenTest() throws Throwable {
571d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        for (Config config : mBaseVariations) {
572d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            getFirstLastChildrenTest(config);
573d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
574d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
575d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
57649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testDontRecycleChildrenOnDetach() throws Throwable {
57749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(false), true);
57849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
57949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
58049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
58149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
58249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
58349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("No views are recycled", recyclerSize,
58449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
58549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
58649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
58749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
58849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
58949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testRecycleChildrenOnDetach() throws Throwable {
59049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(true), true);
59149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        final int childCount = mLayoutManager.getChildCount();
59249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
59349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
59449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
59549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
59649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
59749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mTestAdapter.getItemViewType(0), recyclerSize + childCount);
59849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
59949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("All children should be recycled", childCount + recyclerSize,
60049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
60149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
60249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
60349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
60449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
605d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void getFirstLastChildrenTest(final Config config) throws Throwable {
6068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, true);
607d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Runnable viewInBoundsTest = new Runnable() {
608d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
609d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
610d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
611d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final String boundsLog = mLayoutManager.getBoundsLog();
612d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nfirst visible child should match traversal result\n"
613d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstVisiblePosition,
614d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstVisibleItemPosition()
615d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
616d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
617d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nfirst fully visible child should match traversal result\n"
618d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
619d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
620d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
621d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
622d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nlast visible child should match traversal result\n"
623d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastVisiblePosition,
624d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastVisibleItemPosition()
625d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
626d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
627d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nlast fully visible child should match traversal result\n"
628d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
629d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastCompletelyVisibleItemPosition()
630d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
631d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
632d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
633d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
634d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
635d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // case
636d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
637d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
638d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
639d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
640d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.smoothScrollToPosition(scrollPosition);
641d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
642d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
643d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        while (mLayoutManager.isSmoothScrolling() ||
644d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
645d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            runTestOnUiThread(viewInBoundsTest);
6468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            Thread.sleep(400);
647d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
648d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // delete all items
649d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(2);
650d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
651d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
652d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // test empty case
653d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
654d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // set a new adapter with huge items to test full bounds check
655d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
656d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
657d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final TestAdapter newAdapter = new TestAdapter(100) {
658d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
659d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void onBindViewHolder(TestViewHolder holder,
660d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    int position) {
661d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                super.onBindViewHolder(holder, position);
662310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                if (config.mOrientation == HORIZONTAL) {
663d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumWidth(totalSpace + 5);
664d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                } else {
665d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumHeight(totalSpace + 5);
666d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
667d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
668d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
669d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
670d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
671d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
672d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.setAdapter(newAdapter);
673d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
674d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
675d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
676d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
677d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
678d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
6798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void testSavedState() throws Throwable {
6808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
6818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
6828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
6848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // do nothing
6858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
6898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "doing nothing";
6908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
6928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
6938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
6948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
6958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
6968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
6978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
6988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position";
7038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
7058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
7068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
7088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
7098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
7108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                50);
7118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
7128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with positive offset";
7178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
7198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
7208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
7228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
7238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
7248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                -50);
7258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
7268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with negative offset";
7318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
7338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
7348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
7368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
7378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Doing nothing";
7408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
7428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
7438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
7458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // update config as well so that restore assertions will work
7468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mOrientation = 1 - config.mOrientation;
7478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setOrientation(config.mOrientation);
7488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
7528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
7538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing orientation";
7588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
7608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
7618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
7638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mStackFromEnd = !config.mStackFromEnd;
7648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
7658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
7698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return true; //stack from end should not move items on change
7708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing stack from end";
7758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
7778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
7788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
7808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mReverseLayout = !config.mReverseLayout;
7818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setReverseLayout(config.mReverseLayout);
7828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
7868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
7878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
7888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
7898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
7908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
7918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing reverse layout";
7928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
79375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                },
79475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                new PostRestoreRunnable() {
79549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
79649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
79749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
79849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
79949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
80049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
80149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
80249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
80349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        return true;
80449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
80549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
80649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
80749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    String describe() {
808b97e8219784e623526bc3c6077a698d608f04fd9Yigit Boyar                        return "Change should recycle children";
80949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
81049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                },
81149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                new PostRestoreRunnable() {
81275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    int position;
81375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
81475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
81575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        position = mTestAdapter.getItemCount() / 2;
81675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        mLayoutManager.scrollToPosition(position);
81775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
81875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
81975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
82075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
82175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        return mTestAdapter.getItemCount() == 0;
82275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
82375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
82475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
82575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    String describe() {
826aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                        return "Scroll to position " + position ;
82775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
82875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
82975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
83075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterReLayout(Config config) {
83175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        if (mTestAdapter.getItemCount() > 0) {
832aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                            assertEquals(config + ":scrolled view should be last completely visible",
83375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                                    position,
834aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                    config.mStackFromEnd ?
835aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                            mLayoutManager.findLastCompletelyVisibleItemPosition()
836aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                        : mLayoutManager.findFirstCompletelyVisibleItemPosition());
83775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        }
83875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
8398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
8408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
84175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        boolean[] waitForLayoutOptions = new boolean[]{true, false};
84249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
84349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
84449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        for (Config config : variations) {
8458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
8468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                for (boolean waitForLayout : waitForLayoutOptions) {
8478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
8488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
8498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                postRestoreRunnable);
8508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        removeRecyclerView();
8518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
8528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
8548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
8558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
8578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void savedStateTest(Config config, boolean waitForLayout,
8598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
8608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
8618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (DEBUG) {
8628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
8638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    config + " post layout action " + postLayoutOperation.describe() +
8648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    "post restore action " + postRestoreOperation.describe());
8658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, false);
8678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
8688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
8698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            postLayoutOperation.run();
8708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
8718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
8728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcelable savedState = mRecyclerView.onSaveInstanceState();
8738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // we append a suffix to the parcelable to test out of bounds
8748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        String parcelSuffix = UUID.randomUUID().toString();
8758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcel parcel = Parcel.obtain();
8768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState.writeToParcel(parcel, 0);
8778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.writeString(parcelSuffix);
8788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
8798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // reset for reading
8808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.setDataPosition(0);
8818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // re-create
8828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
8838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
8848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
8858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        RecyclerView restored = new RecyclerView(getActivity());
8868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // this config should be no op.
8878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
888b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar                config.mOrientation, config.mReverseLayout);
889b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
8908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setLayoutManager(mLayoutManager);
8918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // use the same adapter for Rect matching
8928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setAdapter(mTestAdapter);
8938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.onRestoreInstanceState(savedState);
8948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        postRestoreOperation.onAfterRestore(config);
8958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
8968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                parcel.readString());
8978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.expectLayouts(1);
8988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(restored);
8998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.waitForLayout(2);
9008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // calculate prefix here instead of above to include post restore changes
9018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
9028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                "\npostRestore:" + postRestoreOperation.describe() + "\n";
9038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
9048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mReverseLayout, mLayoutManager.getReverseLayout());
9058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, orientation should be preserved",
9068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mOrientation, mLayoutManager.getOrientation());
9078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, stack from end should be preserved",
9088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mStackFromEnd, mLayoutManager.getStackFromEnd());
9098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
9108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (postRestoreOperation.shouldLayoutMatch(config)) {
9118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsEqual(
9128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix + ": on restore, previous view positions should be preserved",
9138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
9148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
9158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsNotEqual(
9168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix
91749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + ": on restore with changes, previous view positions should NOT "
91849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + "be preserved",
9198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
9208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
92175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar            postRestoreOperation.onAfterReLayout(config);
9228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
9238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
9248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
9268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
9278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
9288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
9298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
9308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
9318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
9328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
9338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
9358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Map<Item, Rect> after) {
9368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Throwable throwable = null;
9378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        try {
9388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertRectSetsEqual("NOT " + message, before, after);
9398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        } catch (Throwable t) {
9408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throwable = t;
9418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
9428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
9438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
9448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
9458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
946668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        StringBuilder sb = new StringBuilder();
947668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("checking rectangle equality.");
948668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar         sb.append("before:\n");
949668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
950668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
951668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        }
952668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("after:\n");
953668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
954668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
9558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
956668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        message = message + "\n" + sb.toString();
9578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
9588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                , after.size());
9598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
9608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Rect afterRect = after.get(entry.getKey());
9618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertNotNull(message + ":\nSame item should be visible after simple re-layout",
9628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    afterRect);
9638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
9648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    entry.getValue(), afterRect);
9658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
9668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
9678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
968a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar    public void testAccessibilityPositions() throws Throwable {
969a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
970a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityDelegateCompat delegateCompat = mRecyclerView
971a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                .getCompatAccessibilityDelegate();
972a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityEvent event = AccessibilityEvent.obtain();
973a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        runTestOnUiThread(new Runnable() {
974a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            @Override
975a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            public void run() {
976a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event);
977a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            }
978a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        });
979a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityRecordCompat record = AccessibilityEventCompat
980a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                .asRecord(event);
981a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have first position",
982a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                record.getFromIndex(),
983a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findFirstVisibleItemPosition());
984a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have last position",
985a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                record.getToIndex(),
986a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findLastVisibleItemPosition());
987a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar    }
988a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar
989d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    static class VisibleChildren {
990d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
991d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
992d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
993d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
994d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
995d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
996d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
997d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
998d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
999d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
1000d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
1001d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "VisibleChildren{" +
1002d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
1003d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
1004d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
1005d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
1006d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
1007d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1008d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
1009d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostLayoutRunnable {
10118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract void run() throws Throwable;
10138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
10158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
10168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostRestoreRunnable {
10188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        void onAfterRestore(Config config) throws Throwable {
10208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
10218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
10238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        boolean shouldLayoutMatch(Config config) {
10258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return true;
10268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
102775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
102875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        void onAfterReLayout(Config config) {
102975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
103075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        };
10318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
10328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
1034d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1035d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        CountDownLatch layoutLatch;
1036d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        OrientationHelper mSecondaryOrientation;
10388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        OnLayoutListener mOnLayoutListener;
10406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1041d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
1042d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            super(context, orientation, reverseLayout);
1043d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1044d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1045d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void expectLayouts(int count) {
1046d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch = new CountDownLatch(count);
1047d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1048d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1049d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void waitForLayout(long timeout) throws InterruptedException {
1050d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            waitForLayout(timeout, TimeUnit.SECONDS);
1051d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1052d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
10538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
10548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public void setOrientation(int orientation) {
10558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            super.setOrientation(orientation);
10568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            mSecondaryOrientation = null;
10578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
10588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
10598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
1060504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
1061504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            if (DEBUG) {
1062504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
1063504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
1064504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            super.removeAndRecycleView(child, recycler);
1065504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
1066504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1067504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
1068504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
1069504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            if (DEBUG) {
1070504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
1071504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
1072504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            super.removeAndRecycleViewAt(index, recycler);
1073504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
1074504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1075504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
107694c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar        void ensureLayoutState() {
107794c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar            super.ensureLayoutState();
10788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mSecondaryOrientation == null) {
107994c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
108094c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                        1 - getOrientation());
10818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
10828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
10838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1084d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
10856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
1086d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            assertEquals("all expected layouts should be executed at the expected time",
1087d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    0, layoutLatch.getCount());
10886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            getInstrumentation().waitForIdleSync();
1089d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1090d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1091d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String getBoundsLog() {
1092d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            StringBuilder sb = new StringBuilder();
1093d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
1094d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
1095d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("\nchildren bounds\n");
1096d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int childCount = getChildCount();
1097d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
1098d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
1099d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
1100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append("[").append("start:").append(
1101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
1102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
1103d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
1104d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return sb.toString();
1105d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1106d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1107310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
1108310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
1109310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            if (itemAnimator == null) {
1110310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                return;
1111310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
1112310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            final CountDownLatch latch = new CountDownLatch(1);
1113310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            final boolean running = itemAnimator.isRunning(
1114310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
1115310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        @Override
1116310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        public void onAnimationsFinished() {
1117310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                            latch.countDown();
1118310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                        }
1119310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                    }
1120310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            );
1121310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            if (running) {
1122310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
1123310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            }
1124310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        }
1125310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
1126d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
1127d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            int childCount = getChildCount();
1128d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
1129d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
1130d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
1131d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
1132d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
1133d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
1134d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
1135d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
1136d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
1137d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (hidden) {
1138d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    continue;
1139d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1140d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int position = getPosition(child);
1141d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (fullyVisible) {
1142d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
1143d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
1144d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
1145d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
1146d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1147d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
1148d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
1149d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
1150d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1151d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1152d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
1153d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
1154d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
1155d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1156d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1157d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
1158d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
1159d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
1160d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1161d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
1162d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return visibleChildren;
1163d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1164d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
11658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Rect getViewBounds(View view) {
11668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (getOrientation() == HORIZONTAL) {
11678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
11688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
11698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
11708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
11718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
11728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
11738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
11748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
11758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
11768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
11778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
11788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
11798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
11808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
11818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
11828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
11838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
11848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            runTestOnUiThread(new Runnable() {
11858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                @Override
11868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                public void run() {
11878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    final int childCount = getChildCount();
11888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (int i = 0; i < childCount; i++) {
11898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        View child = getChildAt(i);
11908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
11918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                .getLayoutParams();
11928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
1193115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                        items.put(vh.mBoundItem, getViewBounds(child));
11948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
11958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
11968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            });
11978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return items;
11988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
11998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1200d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
1201d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
12026e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            try {
12036e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (mOnLayoutListener != null) {
12046e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mOnLayoutListener.before(recycler, state);
12056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
12066e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                super.onLayoutChildren(recycler, state);
12076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (mOnLayoutListener != null) {
12086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mOnLayoutListener.after(recycler, state);
12096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
12106e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            } catch (Throwable t) {
12116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                postExceptionToInstrumentation(t);
12126e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
1213d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch.countDown();
1214d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
12156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
12166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
12176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
12186e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
12196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    static class OnLayoutListener {
12206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
12216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
1222d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
1223d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
12248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    static class Config implements Cloneable {
1225d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
12268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private static final int DEFAULT_ITEM_COUNT = 100;
1227d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1228d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private boolean mStackFromEnd;
1229d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1230310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int mOrientation = VERTICAL;
1231d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1232d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        boolean mReverseLayout = false;
1233d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
123449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
123549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
1236d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
1237d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1238504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        TestAdapter mTestAdapter;
1239504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1240d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
1241d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
1242d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
1243d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromEnd;
1244d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1245d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1246d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config() {
1247d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1248d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1249d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1250504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        Config adapter(TestAdapter adapter) {
1251504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            mTestAdapter = adapter;
1252504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            return this;
1253504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
1254504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
125549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
125649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
125749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            return this;
125849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        }
125949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
1260d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config orientation(int orientation) {
1261d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
1262d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1263d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1264d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1265d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
1266d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromBottom;
1267d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1268d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1269d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1270d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
1271d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
1272d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1273d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1274d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
1275d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config itemCount(int itemCount) {
1276d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mItemCount = itemCount;
1277d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
1278d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1279d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
12808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // required by convention
12818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
12828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public Object clone() throws CloneNotSupportedException {
12838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return super.clone();
12848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
12858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1286d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
1287d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
1288d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "Config{" +
1289d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "mStackFromEnd=" + mStackFromEnd +
1290d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mOrientation=" + mOrientation +
1291d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mReverseLayout=" + mReverseLayout +
129249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
1293d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mItemCount=" + mItemCount +
1294d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
1295d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
1296d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
1297542f1260934df280985294eaef1ec8469863281fYigit Boyar
1298542f1260934df280985294eaef1ec8469863281fYigit Boyar    private static class WrappedRecyclerView extends RecyclerView {
1299542f1260934df280985294eaef1ec8469863281fYigit Boyar
1300542f1260934df280985294eaef1ec8469863281fYigit Boyar        public WrappedRecyclerView(Context context) {
1301542f1260934df280985294eaef1ec8469863281fYigit Boyar            super(context);
1302542f1260934df280985294eaef1ec8469863281fYigit Boyar            init(context);
1303542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1304542f1260934df280985294eaef1ec8469863281fYigit Boyar
1305542f1260934df280985294eaef1ec8469863281fYigit Boyar        public WrappedRecyclerView(Context context, AttributeSet attrs) {
1306542f1260934df280985294eaef1ec8469863281fYigit Boyar            super(context, attrs);
1307542f1260934df280985294eaef1ec8469863281fYigit Boyar            init(context);
1308542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1309542f1260934df280985294eaef1ec8469863281fYigit Boyar
1310542f1260934df280985294eaef1ec8469863281fYigit Boyar        public WrappedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
1311542f1260934df280985294eaef1ec8469863281fYigit Boyar            super(context, attrs, defStyle);
1312542f1260934df280985294eaef1ec8469863281fYigit Boyar            init(context);
1313542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1314542f1260934df280985294eaef1ec8469863281fYigit Boyar
1315542f1260934df280985294eaef1ec8469863281fYigit Boyar        private void init(Context context) {
1316542f1260934df280985294eaef1ec8469863281fYigit Boyar            initializeScrollbars(null);
1317542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1318542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
1319d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar}
1320