LinearLayoutManagerTest.java revision 49c83b12201dde5b93d4eca3d44478e0c967a2e6
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;
238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log;
24d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View;
25d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.lang.reflect.Field;
27d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList;
288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.LinkedHashMap;
29d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List;
308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.Map;
318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport java.util.UUID;
32d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.CountDownLatch;
33d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.concurrent.TimeUnit;
34d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/**
368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}.
378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p>
388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation
398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling
408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing.
418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */
42d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
43d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final boolean DEBUG = false;
458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private static final String TAG = "LinearLayoutManagerTest";
478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
48d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    WrappedLinearLayoutManager mLayoutManager;
49d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
50d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    TestAdapter mTestAdapter;
51d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
52d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    final List<Config> mBaseVariations = new ArrayList<Config>();
53d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
54d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    @Override
55d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    protected void setUp() throws Exception {
56d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        super.setUp();
57d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        for (int orientation : new int[]{LinearLayoutManager.VERTICAL,
58d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                LinearLayoutManager.HORIZONTAL}) {
59d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
60d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                for (boolean stackFromBottom : new boolean[]{false, true}) {
61d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
62d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
63d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
64d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
65d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
66d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    protected List<Config> addConfigVariation(List<Config> base, String fieldName,
688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Object... variations)
698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Config config : base) {
738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (Object variation : variations) {
748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Config newConfig = (Config) config.clone();
758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                field.set(newConfig, variation);
768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                newConfigs.add(newConfig);
778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        return newConfigs;
808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView = new RecyclerView(getActivity());
848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setHasFixedSize(true);
85d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mTestAdapter = new TestAdapter(config.mItemCount);
868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setAdapter(mTestAdapter);
87d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
88d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                config.mReverseLayout);
89d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
9049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForFirstLayout) {
938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    private void waitForFirstLayout() throws Throwable {
98d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(mRecyclerView);
100d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
101d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
102d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
103d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
104d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void testGetFirstLastChildrenTest() throws Throwable {
105d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        for (Config config : mBaseVariations) {
106d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            getFirstLastChildrenTest(config);
107d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
108d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
109d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
11049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testDontRecycleChildrenOnDetach() throws Throwable {
11149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(false), true);
11249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
11349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
11449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
11549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
11649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
11749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("No views are recycled", recyclerSize,
11849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
11949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
12049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
12149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
12249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
12349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    public void testRecycleChildrenOnDetach() throws Throwable {
12449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(true), true);
12549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        final int childCount = mLayoutManager.getChildCount();
12649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        runTestOnUiThread(new Runnable() {
12749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
12849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
12949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
13049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
13149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mTestAdapter.getItemViewType(0), recyclerSize + childCount);
13249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.setLayoutManager(new TestLayoutManager());
13349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("All children should be recycled", childCount + recyclerSize,
13449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
13549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
13649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
13749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
13849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
139d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    public void getFirstLastChildrenTest(final Config config) throws Throwable {
1408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, true);
141d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Runnable viewInBoundsTest = new Runnable() {
142d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
143d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
144d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
145d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final String boundsLog = mLayoutManager.getBoundsLog();
146d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nfirst visible child should match traversal result\n"
147d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstVisiblePosition,
148d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstVisibleItemPosition()
149d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
150d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
151d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nfirst fully visible child should match traversal result\n"
152d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
153d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
154d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
155d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
156d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(config + ":\nlast visible child should match traversal result\n"
157d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastVisiblePosition,
158d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastVisibleItemPosition()
159d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
160d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                assertEquals(
161d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        config + ":\nlast fully visible child should match traversal result\n"
162d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
163d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mLayoutManager.findLastCompletelyVisibleItemPosition()
164d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                );
165d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
166d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
167d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
168d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
169d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // case
170d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
171d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
172d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
173d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
174d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.smoothScrollToPosition(scrollPosition);
175d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
176d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
177d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        while (mLayoutManager.isSmoothScrolling() ||
178d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
179d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            runTestOnUiThread(viewInBoundsTest);
1808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            Thread.sleep(400);
181d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
182d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // delete all items
183d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(2);
184d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
185d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
186d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // test empty case
187d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
188d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        // set a new adapter with huge items to test full bounds check
189d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.expectLayouts(1);
190d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
191d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        final TestAdapter newAdapter = new TestAdapter(100) {
192d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
193d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void onBindViewHolder(TestViewHolder holder,
194d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    int position) {
195d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                super.onBindViewHolder(holder, position);
196d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (config.mOrientation == LinearLayoutManager.HORIZONTAL) {
197d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumWidth(totalSpace + 5);
198d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                } else {
199d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    holder.itemView.setMinimumHeight(totalSpace + 5);
200d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
201d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
202d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        };
203d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(new Runnable() {
204d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            @Override
205d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            public void run() {
206d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                mRecyclerView.setAdapter(newAdapter);
207d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
208d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        });
209d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        mLayoutManager.waitForLayout(2);
210d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        runTestOnUiThread(viewInBoundsTest);
211d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
212d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
2138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void testSavedState() throws Throwable {
21449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        Thread.sleep(5000);
2158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
2168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
2178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
2198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // do nothing
2208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "doing nothing";
2258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
2278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
2288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
2308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
2318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
2328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
2338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position";
2388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
2408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
2418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
2438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
2448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
2458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                50);
2468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
2478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with positive offset";
2528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
2548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostLayoutRunnable() {
2558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public void run() throws Throwable {
2578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.expectLayouts(1);
2588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
2598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                -50);
2608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.waitForLayout(2);
2618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "scroll to position with negative offset";
2668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
2688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
2698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
2718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
2728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Doing nothing";
2758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
2778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
2788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
2808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        // update config as well so that restore assertions will work
2818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mOrientation = 1 - config.mOrientation;
2828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setOrientation(config.mOrientation);
2838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
2878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
2888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
2928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing orientation";
2938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
2948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
2958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
2968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
2978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
2988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mStackFromEnd = !config.mStackFromEnd;
2998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
3008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
3038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
3048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return true; //stack from end should not move items on change
3058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
3088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
3098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing stack from end";
3108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                },
3128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                new PostRestoreRunnable() {
3138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
3148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    void onAfterRestore(Config config) throws Throwable {
3158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        config.mReverseLayout = !config.mReverseLayout;
3168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mLayoutManager.setReverseLayout(config.mReverseLayout);
3178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
3208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    boolean shouldLayoutMatch(Config config) {
3218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return config.mItemCount == 0;
3228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    @Override
3258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    public String describe() {
3268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        return "Changing reverse layout";
3278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
32875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                },
32975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                new PostRestoreRunnable() {
33049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
33149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
33249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
33349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
33449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
33549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
33649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
33749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
33849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        return true;
33949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
34049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
34149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    @Override
34249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    String describe() {
34349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        return "Change shoudl recycle children";
34449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    }
34549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                },
34649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                new PostRestoreRunnable() {
34775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    int position;
34875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
34975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterRestore(Config config) throws Throwable {
35075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        position = mTestAdapter.getItemCount() / 2;
35175b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        mLayoutManager.scrollToPosition(position);
35275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
35375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
35475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
35575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    boolean shouldLayoutMatch(Config config) {
35675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        return mTestAdapter.getItemCount() == 0;
35775b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
35875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
35975b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
36075b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    String describe() {
361aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                        return "Scroll to position " + position ;
36275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
36375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
36475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    @Override
36575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    void onAfterReLayout(Config config) {
36675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        if (mTestAdapter.getItemCount() > 0) {
367aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                            assertEquals(config + ":scrolled view should be last completely visible",
36875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                                    position,
369aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                    config.mStackFromEnd ?
370aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                            mLayoutManager.findLastCompletelyVisibleItemPosition()
371aeb27f42f502f1687a7047bc34deeff17b874287Yigit Boyar                                        : mLayoutManager.findFirstCompletelyVisibleItemPosition());
37275b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                        }
37375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar                    }
3748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
3758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        };
37675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        boolean[] waitForLayoutOptions = new boolean[]{true, false};
37749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
37849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
37949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        for (Config config : variations) {
3808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
3818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                for (boolean waitForLayout : waitForLayoutOptions) {
3828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
3838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
3848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                postRestoreRunnable);
3858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        removeRecyclerView();
3868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
3878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
3898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
3918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void savedStateTest(Config config, boolean waitForLayout,
3948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
3958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
3968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (DEBUG) {
3978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
3988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    config + " post layout action " + postLayoutOperation.describe() +
3998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    "post restore action " + postRestoreOperation.describe());
4008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
4018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setupByConfig(config, false);
4028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
4038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            waitForFirstLayout();
4048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            postLayoutOperation.run();
4058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
4068edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
4078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcelable savedState = mRecyclerView.onSaveInstanceState();
4088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // we append a suffix to the parcelable to test out of bounds
4098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        String parcelSuffix = UUID.randomUUID().toString();
4108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Parcel parcel = Parcel.obtain();
4118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState.writeToParcel(parcel, 0);
4128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.writeString(parcelSuffix);
4138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
4148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // reset for reading
4158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        parcel.setDataPosition(0);
4168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // re-create
4178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
4188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        removeRecyclerView();
4198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
4208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        RecyclerView restored = new RecyclerView(getActivity());
4218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // this config should be no op.
4228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
4238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                1 - config.mOrientation, !config.mReverseLayout);
4248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.setStackFromEnd(!config.mStackFromEnd);
4258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setLayoutManager(mLayoutManager);
4268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // use the same adapter for Rect matching
4278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.setAdapter(mTestAdapter);
4288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        restored.onRestoreInstanceState(savedState);
4298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        postRestoreOperation.onAfterRestore(config);
4308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
4318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                parcel.readString());
4328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.expectLayouts(1);
4338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        setRecyclerView(restored);
4348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        mLayoutManager.waitForLayout(2);
4358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // calculate prefix here instead of above to include post restore changes
4368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
4378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                "\npostRestore:" + postRestoreOperation.describe() + "\n";
4388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
4398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mReverseLayout, mLayoutManager.getReverseLayout());
4408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, orientation should be preserved",
4418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mOrientation, mLayoutManager.getOrientation());
4428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(logPrefix + " on saved state, stack from end should be preserved",
4438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                config.mStackFromEnd, mLayoutManager.getStackFromEnd());
44449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        assertEquals(logPrefix + " on saved state, mRecycleChildrenOnDetach should be preserved",
44549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                config.mRecycleChildrenOnDetach, mLayoutManager.getRecycleChildrenOnDetach());
4468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        if (waitForLayout) {
4478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (postRestoreOperation.shouldLayoutMatch(config)) {
4488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsEqual(
4498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix + ": on restore, previous view positions should be preserved",
4508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
4518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
4528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                assertRectSetsNotEqual(
4538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        logPrefix
45449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + ": on restore with changes, previous view positions should NOT "
45549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                                + "be preserved",
4568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        before, mLayoutManager.collectChildCoordinates());
4578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
45875b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar            postRestoreOperation.onAfterReLayout(config);
4598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
4608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
4618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
4628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
4638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
4648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
4658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
4668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
4678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
4688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
4698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
4708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
4718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
4728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Map<Item, Rect> after) {
4738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Throwable throwable = null;
4748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        try {
4758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertRectSetsEqual("NOT " + message, before, after);
4768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        } catch (Throwable t) {
4778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throwable = t;
4788edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
4798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertNotNull(message + "\ntwo layout should be different", throwable);
4808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
4818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
4828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
483668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        StringBuilder sb = new StringBuilder();
484668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("checking rectangle equality.");
485668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar         sb.append("before:\n");
486668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
487668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
488668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        }
489668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        sb.append("after:\n");
490668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
491668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
4928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
493668e774379c036a5d53d07ec69ed9ebee13a1fd9Yigit Boyar        message = message + "\n" + sb.toString();
4948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        assertEquals(message + ":\nitem counts should be equal", before.size()
4958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                , after.size());
4968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
4978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Rect afterRect = after.get(entry.getKey());
4988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertNotNull(message + ":\nSame item should be visible after simple re-layout",
4998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    afterRect);
5008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            assertEquals(message + ":\nItem should be laid out at the same coordinates",
5018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    entry.getValue(), afterRect);
5028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
5038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
5048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
505d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    static class VisibleChildren {
506d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
507d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstVisiblePosition = RecyclerView.NO_POSITION;
508d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
509d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
510d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
511d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastVisiblePosition = RecyclerView.NO_POSITION;
512d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
513d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
514d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
515d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
516d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
517d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "VisibleChildren{" +
518d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "firstVisiblePosition=" + firstVisiblePosition +
519d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
520d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastVisiblePosition=" + lastVisiblePosition +
521d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
522d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
523d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
524d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
525d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
5268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostLayoutRunnable {
5278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract void run() throws Throwable;
5298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
5318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
5328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    abstract private class PostRestoreRunnable {
5348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        void onAfterRestore(Config config) throws Throwable {
5368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
5378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        abstract String describe();
5398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        boolean shouldLayoutMatch(Config config) {
5418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return true;
5428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
54375b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
54475b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        void onAfterReLayout(Config config) {
54575b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar
54675b7ff9ccca9311854e9c74282b1af1ce87df470Yigit Boyar        };
5478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
5488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    class WrappedLinearLayoutManager extends LinearLayoutManager {
550d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
551d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        CountDownLatch layoutLatch;
552d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
5538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        OrientationHelper mSecondaryOrientation;
5548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
555d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
556d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            super(context, orientation, reverseLayout);
557d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
558d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
559d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void expectLayouts(int count) {
560d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch = new CountDownLatch(count);
561d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
562d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
563d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void waitForLayout(long timeout) throws InterruptedException {
564d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            waitForLayout(timeout, TimeUnit.SECONDS);
565d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
566d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
5678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
5688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public void setOrientation(int orientation) {
5698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            super.setOrientation(orientation);
5708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            mSecondaryOrientation = null;
5718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
5728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
5738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
57494c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar        void ensureLayoutState() {
57594c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar            super.ensureLayoutState();
5768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mSecondaryOrientation == null) {
57794c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
57894c0b086c12e634976fecd47d442bc7a1a6341bbYigit Boyar                        1 - getOrientation());
5798edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
5808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
5818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
582d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
583d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch.await(timeout, timeUnit);
584d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            assertEquals("all expected layouts should be executed at the expected time",
585d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    0, layoutLatch.getCount());
586d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
587d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
588d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String getBoundsLog() {
589d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            StringBuilder sb = new StringBuilder();
590d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
591d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
592d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            sb.append("\nchildren bounds\n");
593d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int childCount = getChildCount();
594d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
595d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
596d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
597d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append("[").append("start:").append(
598d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
599d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
600d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
601d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return sb.toString();
602d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
603d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
604d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
605d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            int childCount = getChildCount();
606d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren();
607d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int start = mOrientationHelper.getStartAfterPadding();
608d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            final int end = mOrientationHelper.getEndAfterPadding();
609d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            for (int i = 0; i < childCount; i++) {
610d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                View child = getChildAt(i);
611d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childStart = mOrientationHelper.getDecoratedStart(child);
612d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
613d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
614d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
615d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (hidden) {
616d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    continue;
617d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
618d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                final int position = getPosition(child);
619d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (fullyVisible) {
620d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePosition ||
621d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
622d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstFullyVisiblePosition = position;
623d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
624d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
625d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePosition) {
626d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.lastFullyVisiblePosition = position;
627d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    }
628d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
629d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
630d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position < visibleChildren.firstVisiblePosition ||
631d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
632d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.firstVisiblePosition = position;
633d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
634d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
635d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                if (position > visibleChildren.lastVisiblePosition) {
636d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    visibleChildren.lastVisiblePosition = position;
637d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                }
638d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
639d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            }
640d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return visibleChildren;
641d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
642d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
6438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Rect getViewBounds(View view) {
6448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (getOrientation() == HORIZONTAL) {
6458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
6468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
6478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
6488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view),
6498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
6508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            } else {
6518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                return new Rect(
6528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
6538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedStart(view),
6548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
6558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        mOrientationHelper.getDecoratedEnd(view));
6568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
6578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
6598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
6608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
6618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
6628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            runTestOnUiThread(new Runnable() {
6638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                @Override
6648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                public void run() {
6658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    final int childCount = getChildCount();
6668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    for (int i = 0; i < childCount; i++) {
6678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        View child = getChildAt(i);
6688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
6698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                                .getLayoutParams();
6708edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
6718edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                        items.put(vh.mBindedItem, getViewBounds(child));
6728edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                    }
6738edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                }
6748edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            });
6758edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return items;
6768edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
6778edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
678d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
679d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
680d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            super.onLayoutChildren(recycler, state);
681d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            layoutLatch.countDown();
682d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
683d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
684d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
6858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    static class Config implements Cloneable {
686d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
6878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private static final int DEFAULT_ITEM_COUNT = 100;
688d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
689d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        private boolean mStackFromEnd;
690d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
691d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int mOrientation = LinearLayoutManager.VERTICAL;
692d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
693d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        boolean mReverseLayout = false;
694d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
69549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        boolean mRecycleChildrenOnDetach = false;
69649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
697d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
698d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
699d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
700d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
701d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
702d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromEnd;
703d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
704d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
705d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config() {
706d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
707d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
708d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
70949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
71049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
71149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            return this;
71249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        }
71349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
714d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config orientation(int orientation) {
715d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mOrientation = orientation;
716d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
717d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
718d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
719d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config stackFromBottom(boolean stackFromBottom) {
720d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mStackFromEnd = stackFromBottom;
721d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
722d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
723d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
724d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
725d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mReverseLayout = reverseLayout;
726d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
727d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
728d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
729d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public Config itemCount(int itemCount) {
730d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            mItemCount = itemCount;
731d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return this;
732d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
733d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
7348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        // required by convention
7358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        @Override
7368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        public Object clone() throws CloneNotSupportedException {
7378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            return super.clone();
7388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
7398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
740d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        @Override
741d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        public String toString() {
742d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar            return "Config{" +
743d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    "mStackFromEnd=" + mStackFromEnd +
744d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mOrientation=" + mOrientation +
745d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mReverseLayout=" + mReverseLayout +
74649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
747d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    ", mItemCount=" + mItemCount +
748d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar                    '}';
749d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar        }
750d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar    }
751d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar}
752