BaseStaggeredGridLayoutManagerTest.java revision bc61003e6a10872c23f0de155456cb2fbeef31a7
10a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpackage android.support.v7.widget;
20a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
30a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.graphics.Rect;
40a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.support.annotation.Nullable;
50a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.util.Log;
60a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.view.View;
70a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.view.ViewGroup;
80a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
90a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.lang.reflect.Field;
100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.ArrayList;
110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.Arrays;
120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.HashSet;
130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.LinkedHashMap;
140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.List;
150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.Map;
160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.concurrent.CountDownLatch;
170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.concurrent.TimeUnit;
180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_END;
210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.LayoutState.LAYOUT_START;
220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertEquals;
270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertFalse;
280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertNotNull;
290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.assertTrue;
300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpublic class BaseStaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected static final boolean DEBUG = false;
340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected static final int AVG_ITEM_PER_VIEW = 3;
350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected static final String TAG = "StaggeredGridLayoutManagerTest";
360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    volatile WrappedLayoutManager mLayoutManager;
370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    GridTestAdapter mAdapter;
380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected static List<Config> createBaseVariations() {
400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        List<Config> variations = new ArrayList<>();
410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (boolean reverseLayout : new boolean[]{false, true}) {
430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                for (int spanCount : new int[]{1, 3}) {
440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
464143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        for (boolean wrap : new boolean[]{true, false}) {
474143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                            variations.add(new Config(orientation, reverseLayout, spanCount,
484143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                                    gapStrategy).wrap(wrap));
494143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        }
504143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return variations;
560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            Object... variations)
600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        List<Config> newConfigs = new ArrayList<Config>();
620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Field field = Config.class.getDeclaredField(fieldName);
630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        for (Config config : base) {
640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (Object variation : variations) {
650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                Config newConfig = (Config) config.clone();
660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                field.set(newConfig, variation);
670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                newConfigs.add(newConfig);
680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return newConfigs;
710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    void setupByConfig(Config config) throws Throwable {
740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        setupByConfig(config, new GridTestAdapter(config.mItemCount, config.mOrientation));
750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    void setupByConfig(Config config, GridTestAdapter adapter) throws Throwable {
780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mAdapter = adapter;
790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mRecyclerView = new RecyclerView(getActivity());
800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mRecyclerView.setAdapter(mAdapter);
810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mRecyclerView.setHasFixedSize(true);
820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                config.mOrientation);
840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.setGapStrategy(config.mGapStrategy);
850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.setReverseLayout(config.mReverseLayout);
860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mRecyclerView.setLayoutManager(mLayoutManager);
870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            @Override
890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    RecyclerView.State state) {
910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                try {
920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    StaggeredGridLayoutManager.LayoutParams
930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    assertNotNull("view should have layout params assigned", lp);
950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    assertNotNull("when item offsets are requested, view should have a valid span",
960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            lp.mSpan);
970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                } catch (Throwable t) {
980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    postExceptionToInstrumentation(t);
990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
1000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
1010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        });
1020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    StaggeredGridLayoutManager.LayoutParams getLp(View view) {
1050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
1060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    void waitFirstLayout() throws Throwable {
1090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.expectLayouts(1);
1100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        setRecyclerView(mRecyclerView);
1110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.waitForLayout(2);
1120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        getInstrumentation().waitForIdleSync();
1130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    /**
1160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar     * enqueues an empty runnable to main thread so that we can be assured it did run
1170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar     *
1180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar     * @param count Number of times to run
1190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar     */
1200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected void waitForMainThread(int count) throws Throwable {
1210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final AtomicInteger i = new AtomicInteger(count);
1220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        while (i.get() > 0) {
1230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            runTestOnUiThread(new Runnable() {
1240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                @Override
1250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                public void run() {
1260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    i.decrementAndGet();
1270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
1280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            });
1290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
1300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
1330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            Map<Item, Rect> after) {
1340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Throwable throwable = null;
1350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        try {
1360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            assertRectSetsEqual("NOT " + message, before, after);
1370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        } catch (Throwable t) {
1380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            throwable = t;
1390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
1400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertNotNull(message + " two layout should be different", throwable);
1410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
1440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertRectSetsEqual(message, before, after, true);
1450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
1480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            boolean strictItemEquality) {
1490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        StringBuilder log = new StringBuilder();
1500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        if (DEBUG) {
1510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            log.append("checking rectangle equality.\n");
1520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            log.append("total space:" + mLayoutManager.mPrimaryOrientation.getTotalSpace());
1530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            log.append("before:");
1540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (Map.Entry<Item, Rect> entry : before.entrySet()) {
1550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
1560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        .append(entry.getValue());
1570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
1580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            log.append("\nafter:");
1590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (Map.Entry<Item, Rect> entry : after.entrySet()) {
1600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
1610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        .append(entry.getValue());
1620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
1630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            message += "\n\n" + log.toString();
1640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
1650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertEquals(message + ": item counts should be equal", before.size()
1660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                , after.size());
1670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
1680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final Item beforeItem = entry.getKey();
1690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            Rect afterRect = null;
1700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (strictItemEquality) {
1710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                afterRect = after.get(beforeItem);
1720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                assertNotNull(message + ": Same item should be visible after simple re-layout",
1730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        afterRect);
1740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } else {
1750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
1760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    final Item afterItem = afterEntry.getKey();
1770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
1780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        afterRect = afterEntry.getValue();
1790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        break;
1800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
1810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
1820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                assertNotNull(message + ": Item with same adapter index should be visible " +
1830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                "after simple re-layout",
1840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        afterRect);
1850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
1860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            assertEquals(message + ": Item should be laid out at the same coordinates",
1870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    entry.getValue(),
1880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    afterRect);
1890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
1900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected void assertViewPositions(Config config) {
1930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        ArrayList<ArrayList<View>> viewsBySpan = mLayoutManager.collectChildrenBySpan();
1940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        OrientationHelper orientationHelper = OrientationHelper
1950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                .createOrientationHelper(mLayoutManager, config.mOrientation);
1960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        for (ArrayList<View> span : viewsBySpan) {
1970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            // validate all children's order. first child should have min start mPosition
1980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final int count = span.size();
1990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0, j = 1; j < count; i++, j++) {
2000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                View prev = span.get(i);
2010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                View next = span.get(j);
2020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                assertTrue(config + " prev item should be above next item",
2030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        orientationHelper.getDecoratedEnd(prev) <= orientationHelper
2040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                .getDecoratedStart(next)
2050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                );
2060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
2100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected TargetTuple findInvisibleTarget(Config config) {
2120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
2130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
2140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            View child = mLayoutManager.getChildAt(i);
2150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            int position = mRecyclerView.getChildLayoutPosition(child);
2160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (position < minPosition) {
2170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                minPosition = position;
2180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (position > maxPosition) {
2200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                maxPosition = position;
2210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int tailTarget = maxPosition + (mAdapter.getItemCount() - maxPosition) / 2;
2240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int headTarget = minPosition / 2;
2250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int target;
2260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        // where will the child come from ?
2270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int itemLayoutDirection;
2280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
2290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            target = tailTarget;
2300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
2310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        } else {
2320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            target = headTarget;
2330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
2340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        if (DEBUG) {
2360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            Log.d(TAG,
2370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
2380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return new TargetTuple(target, itemLayoutDirection);
2400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
2410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected void scrollToPositionWithOffset(final int position, final int offset)
2430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            throws Throwable {
2440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        runTestOnUiThread(new Runnable() {
2450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            @Override
2460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            public void run() {
2470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mLayoutManager.scrollToPositionWithOffset(position, offset);
2480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        });
2500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
2510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    static class OnLayoutListener {
2530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
2550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
2580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
2600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    static class VisibleChildren {
2620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int[] firstVisiblePositions;
2640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int[] firstFullyVisiblePositions;
2660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int[] lastVisiblePositions;
2680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int[] lastFullyVisiblePositions;
2700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        View findFirstPartialVisibleClosestToStart;
2720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        View findFirstPartialVisibleClosestToEnd;
2730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        VisibleChildren(int spanCount) {
2750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            firstFullyVisiblePositions = new int[spanCount];
2760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            firstVisiblePositions = new int[spanCount];
2770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            lastVisiblePositions = new int[spanCount];
2780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            lastFullyVisiblePositions = new int[spanCount];
2790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < spanCount; i++) {
2800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                firstFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
2810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                firstVisiblePositions[i] = RecyclerView.NO_POSITION;
2820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lastVisiblePositions[i] = RecyclerView.NO_POSITION;
2830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lastFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
2840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
2860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
2880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public boolean equals(Object o) {
2890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (this == o) {
2900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return true;
2910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (o == null || getClass() != o.getClass()) {
2930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
2940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
2950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            VisibleChildren that = (VisibleChildren) o;
2970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (!Arrays.equals(firstFullyVisiblePositions, that.firstFullyVisiblePositions)) {
2990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (findFirstPartialVisibleClosestToStart
3020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    != null ? !findFirstPartialVisibleClosestToStart
3030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .equals(that.findFirstPartialVisibleClosestToStart)
3040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    : that.findFirstPartialVisibleClosestToStart != null) {
3050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (!Arrays.equals(firstVisiblePositions, that.firstVisiblePositions)) {
3080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (!Arrays.equals(lastFullyVisiblePositions, that.lastFullyVisiblePositions)) {
3110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (findFirstPartialVisibleClosestToEnd != null ? !findFirstPartialVisibleClosestToEnd
3140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .equals(that.findFirstPartialVisibleClosestToEnd)
3150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    : that.findFirstPartialVisibleClosestToEnd
3160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            != null) {
3170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (!Arrays.equals(lastVisiblePositions, that.lastVisiblePositions)) {
3200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return false;
3210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return true;
3240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
3270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public int hashCode() {
3280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            int result = Arrays.hashCode(firstVisiblePositions);
3290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            result = 31 * result + Arrays.hashCode(firstFullyVisiblePositions);
3300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            result = 31 * result + Arrays.hashCode(lastVisiblePositions);
3310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            result = 31 * result + Arrays.hashCode(lastFullyVisiblePositions);
3320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            result = 31 * result + (findFirstPartialVisibleClosestToStart != null
3330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ? findFirstPartialVisibleClosestToStart
3340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .hashCode() : 0);
3350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            result = 31 * result + (findFirstPartialVisibleClosestToEnd != null
3360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ? findFirstPartialVisibleClosestToEnd
3370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .hashCode()
3380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    : 0);
3390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return result;
3400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
3430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public String toString() {
3440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return "VisibleChildren{" +
3450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    "firstVisiblePositions=" + Arrays.toString(firstVisiblePositions) +
3460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ", firstFullyVisiblePositions=" + Arrays.toString(firstFullyVisiblePositions) +
3470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ", lastVisiblePositions=" + Arrays.toString(lastVisiblePositions) +
3480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ", lastFullyVisiblePositions=" + Arrays.toString(lastFullyVisiblePositions) +
3490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ", findFirstPartialVisibleClosestToStart=" +
3500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    viewToString(findFirstPartialVisibleClosestToStart) +
3510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    ", findFirstPartialVisibleClosestToEnd=" +
3520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    viewToString(findFirstPartialVisibleClosestToEnd) +
3530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    '}';
3540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        private String viewToString(View view) {
3570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (view == null) {
3580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return null;
3590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            ViewGroup.LayoutParams lp = view.getLayoutParams();
3610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (lp instanceof RecyclerView.LayoutParams == false) {
3620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return System.identityHashCode(view) + "(?)";
3630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
3640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            RecyclerView.LayoutParams rvlp = (RecyclerView.LayoutParams) lp;
3650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return System.identityHashCode(view) + "(" + rvlp.getViewAdapterPosition() + ")";
3660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
3680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    abstract static class OnBindCallback {
3700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        abstract void onBoundItem(TestViewHolder vh, int position);
3720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        boolean assignRandomSize() {
3740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return true;
3750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        void onCreatedViewHolder(TestViewHolder vh) {
3780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
3790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
3800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    static class Config implements Cloneable {
3820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        static final int DEFAULT_ITEM_COUNT = 300;
3840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mOrientation = OrientationHelper.VERTICAL;
3860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        boolean mReverseLayout = false;
3880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mSpanCount = 3;
3900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
3920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mItemCount = DEFAULT_ITEM_COUNT;
3940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
3954143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        boolean mWrap = false;
3964143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
3970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
3980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mOrientation = orientation;
3990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mReverseLayout = reverseLayout;
4000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mSpanCount = spanCount;
4010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mGapStrategy = gapStrategy;
4020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public Config() {
4050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Config orientation(int orientation) {
4090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mOrientation = orientation;
4100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return this;
4110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Config reverseLayout(boolean reverseLayout) {
4140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mReverseLayout = reverseLayout;
4150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return this;
4160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Config spanCount(int spanCount) {
4190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mSpanCount = spanCount;
4200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return this;
4210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Config gapStrategy(int gapStrategy) {
4240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mGapStrategy = gapStrategy;
4250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return this;
4260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public Config itemCount(int itemCount) {
4290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mItemCount = itemCount;
4300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return this;
4310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4334143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        public Config wrap(boolean wrap) {
4344143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            mWrap = wrap;
4354143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            return this;
4364143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar        }
4374143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar
4380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
4390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public String toString() {
4400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return "[CONFIG:" +
4410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    " span:" + mSpanCount + "," +
4420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    " orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
4430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    " reverse:" + (mReverseLayout ? "T" : "F") +
4440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    " itemCount:" + mItemCount +
4454143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                    " wrapContent:" + mWrap +
4460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    " gap strategy: " + gapStrategyName(mGapStrategy);
4470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        protected static String gapStrategyName(int gapStrategy) {
4500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            switch (gapStrategy) {
4510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                case GAP_HANDLING_NONE:
4520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    return "none";
4530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
4540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    return "move spans";
4550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
4560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return "gap strategy: unknown";
4570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
4600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public Object clone() throws CloneNotSupportedException {
4610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return super.clone();
4620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
4640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    class WrappedLayoutManager extends StaggeredGridLayoutManager {
4660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        CountDownLatch layoutLatch;
4680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        OnLayoutListener mOnLayoutListener;
4690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        // gradle does not yet let us customize manifest for tests which is necessary to test RTL.
4700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        // until bug is fixed, we'll fake it.
4710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        // public issue id: 57819
4720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Boolean mFakeRTL;
4730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
4750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        boolean isLayoutRTL() {
4760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
4770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void expectLayouts(int count) {
4800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            layoutLatch = new CountDownLatch(count);
4810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
483bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
4840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            waitForLayout(timeout * (DEBUG ? 1000 : 1), TimeUnit.SECONDS);
4850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
487bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
4880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            layoutLatch.await(timeout, timeUnit);
489bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar            checkForMainThreadException();
4900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            assertEquals("all expected layouts should be executed at the expected time",
4910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    0, layoutLatch.getCount());
4920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
4950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
4960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
4970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
4980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
4990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
5000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
5010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            String before;
5020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (DEBUG) {
5030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                before = layoutToString("before");
5040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } else {
5050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                before = "enable DEBUG";
5060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            try {
5080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (mOnLayoutListener != null) {
5090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    mOnLayoutListener.before(recycler, state);
5100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
5110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                super.onLayoutChildren(recycler, state);
5120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (mOnLayoutListener != null) {
5130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    mOnLayoutListener.after(recycler, state);
5140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
5150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                validateChildren(before);
5160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } catch (Throwable t) {
5170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                postExceptionToInstrumentation(t);
5180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            layoutLatch.countDown();
5210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
5240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
5250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            try {
5260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                int result = super.scrollBy(dt, recycler, state);
5270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                validateChildren();
5280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return result;
5290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } catch (Throwable t) {
5300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                postExceptionToInstrumentation(t);
5310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return 0;
5340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public WrappedLayoutManager(int spanCount, int orientation) {
5370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            super(spanCount, orientation);
5380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        ArrayList<ArrayList<View>> collectChildrenBySpan() {
5410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            ArrayList<ArrayList<View>> viewsBySpan = new ArrayList<ArrayList<View>>();
5420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < getSpanCount(); i++) {
5430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                viewsBySpan.add(new ArrayList<View>());
5440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < getChildCount(); i++) {
5460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                View view = getChildAt(i);
5470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                LayoutParams lp
5480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        = (LayoutParams) view
5490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        .getLayoutParams();
5500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                viewsBySpan.get(lp.mSpan.mIndex).add(view);
5510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return viewsBySpan;
5530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Nullable
5560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
5570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
5580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                RecyclerView.State state) {
5590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            View result = null;
5600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            try {
5610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                result = super.onFocusSearchFailed(focused, direction, recycler, state);
5620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                validateChildren();
5630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } catch (Throwable t) {
5640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                postExceptionToInstrumentation(t);
5650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return result;
5670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Rect getViewBounds(View view) {
5700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (getOrientation() == HORIZONTAL) {
5710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return new Rect(
5720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedStart(view),
5730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
5740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedEnd(view),
5750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view));
5760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } else {
5770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return new Rect(
5780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mSecondaryOrientation.getDecoratedStart(view),
5790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedStart(view),
5800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mSecondaryOrientation.getDecoratedEnd(view),
5810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedEnd(view));
5820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
5840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public String getBoundsLog() {
5860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            StringBuilder sb = new StringBuilder();
5870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            sb.append("view bounds:[start:").append(mPrimaryOrientation.getStartAfterPadding())
5880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .append(",").append(" end").append(mPrimaryOrientation.getEndAfterPadding());
5890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            sb.append("\nchildren bounds\n");
5900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final int childCount = getChildCount();
5910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < childCount; i++) {
5920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                View child = getChildAt(i);
5930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
5940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        .append("[").append("start:").append(
5950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedStart(child)).append(", end:")
5960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        .append(mPrimaryOrientation.getDecoratedEnd(child)).append("]\n");
5970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
5980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return sb.toString();
5990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public VisibleChildren traverseAndFindVisibleChildren() {
6020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            int childCount = getChildCount();
6030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final VisibleChildren visibleChildren = new VisibleChildren(getSpanCount());
6040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final int start = mPrimaryOrientation.getStartAfterPadding();
6050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final int end = mPrimaryOrientation.getEndAfterPadding();
6060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < childCount; i++) {
6070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                View child = getChildAt(i);
6080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
6090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
6100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final boolean fullyVisible = childStart >= start && childEnd <= end;
6110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final boolean hidden = childEnd <= start || childStart >= end;
6120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (hidden) {
6130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    continue;
6140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final int position = getPosition(child);
6160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final int span = getLp(child).getSpanIndex();
6170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (fullyVisible) {
6180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    if (position < visibleChildren.firstFullyVisiblePositions[span] ||
6190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            visibleChildren.firstFullyVisiblePositions[span]
6200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                    == RecyclerView.NO_POSITION) {
6210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        visibleChildren.firstFullyVisiblePositions[span] = position;
6220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
6230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    if (position > visibleChildren.lastFullyVisiblePositions[span]) {
6250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        visibleChildren.lastFullyVisiblePositions[span] = position;
6260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
6270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (position < visibleChildren.firstVisiblePositions[span] ||
6300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        visibleChildren.firstVisiblePositions[span] == RecyclerView.NO_POSITION) {
6310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    visibleChildren.firstVisiblePositions[span] = position;
6320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (position > visibleChildren.lastVisiblePositions[span]) {
6350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    visibleChildren.lastVisiblePositions[span] = position;
6360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (visibleChildren.findFirstPartialVisibleClosestToStart == null) {
6380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    visibleChildren.findFirstPartialVisibleClosestToStart = child;
6390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                visibleChildren.findFirstPartialVisibleClosestToEnd = child;
6410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
6420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return visibleChildren;
6430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Map<Item, Rect> collectChildCoordinates() throws Throwable {
6460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
6470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            runTestOnUiThread(new Runnable() {
6480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                @Override
6490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                public void run() {
6500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    final int childCount = getChildCount();
6510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    for (int i = 0; i < childCount; i++) {
6520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        View child = getChildAt(i);
6530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        // do it if and only if child is visible
6540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        if (child.getRight() < 0 || child.getBottom() < 0 ||
6550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                child.getLeft() >= getWidth() || child.getTop() >= getHeight()) {
6560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            // invisible children may be drawn in cases like scrolling so we should
6570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            // ignore them
6580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            continue;
6590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        }
6600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        LayoutParams lp = (LayoutParams) child
6610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                .getLayoutParams();
6620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
6630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        items.put(vh.mBoundItem, getViewBounds(child));
6640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
6650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
6660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            });
6670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return items;
6680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void setFakeRtl(Boolean fakeRtl) {
6720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mFakeRTL = fakeRtl;
6730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            try {
6740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                requestLayoutOnUIThread(mRecyclerView);
6750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } catch (Throwable throwable) {
6760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                postExceptionToInstrumentation(throwable);
6770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
6780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        String layoutToString(String hint) {
6810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            StringBuilder sb = new StringBuilder();
6820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            sb.append("LAYOUT POSITIONS AND INDICES ").append(hint).append("\n");
6830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            for (int i = 0; i < getChildCount(); i++) {
6840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final View view = getChildAt(i);
6850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
6860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                sb.append(String.format("index: %d pos: %d top: %d bottom: %d span: %d isFull:%s",
6870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        i, getPosition(view),
6880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedStart(view),
6890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mPrimaryOrientation.getDecoratedEnd(view),
6900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        layoutParams.getSpanIndex(), layoutParams.isFullSpan())).append("\n");
6910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
6920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return sb.toString();
6930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        protected void validateChildren() {
6960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            validateChildren(null);
6970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
6980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
6990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        private void validateChildren(String msg) {
7000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (getChildCount() == 0 || mRecyclerView.mState.isPreLayout()) {
7010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return;
7020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            final int dir = mShouldReverseLayout ? -1 : 1;
7040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            int i = 0;
7050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            int pos = -1;
7060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            while (i < getChildCount()) {
7070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
7080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (lp.isItemRemoved()) {
7090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    i++;
7100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    continue;
7110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
7120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                pos = getPosition(getChildAt(i));
7130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                break;
7140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (pos == -1) {
7160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                return;
7170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            while (++i < getChildCount()) {
7190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
7200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (lp.isItemRemoved()) {
7210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    continue;
7220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
7230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                pos += dir;
7240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (getPosition(getChildAt(i)) != pos) {
7250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    throw new RuntimeException("INVALID POSITION FOR CHILD " + i + "\n" +
7260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                            layoutToString("ERROR") + "\n msg:" + msg);
7270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
7280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
7300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
7310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    class GridTestAdapter extends TestAdapter {
7330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mOrientation;
7350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mRecyclerViewWidth;
7360a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        int mRecyclerViewHeight;
7370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        Integer mSizeReference = null;
7380a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        // original ids of items that should be full span
7400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        HashSet<Integer> mFullSpanItems = new HashSet<Integer>();
7410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        protected boolean mViewsHaveEqualSize = false; // size in the scrollable direction
7430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        protected OnBindCallback mOnBindCallback;
7450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        GridTestAdapter(int count, int orientation) {
7470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            super(count);
7480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mOrientation = orientation;
7490a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
7500a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
7520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public TestViewHolder onCreateViewHolder(ViewGroup parent,
7530a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                int viewType) {
7540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mRecyclerViewWidth = parent.getWidth();
7550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            mRecyclerViewHeight = parent.getHeight();
7560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            TestViewHolder vh = super.onCreateViewHolder(parent, viewType);
7570a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (mOnBindCallback != null) {
7580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mOnBindCallback.onCreatedViewHolder(vh);
7590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7600a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            return vh;
7610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
7620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
7640a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
7650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (mFullSpanItems.size() > 0) {
7660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                HashSet<Integer> old = mFullSpanItems;
7670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mFullSpanItems = new HashSet<Integer>();
7680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                for (Integer i : old) {
7690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    if (i < start) {
7700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mFullSpanItems.add(i);
7710a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    } else if (offset > 0 || (start + Math.abs(offset)) <= i) {
7720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mFullSpanItems.add(i + offset);
7730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    } else if (DEBUG) {
7740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        Log.d(TAG, "removed full span item " + i);
7750a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    }
7760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
7770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7780a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            super.offsetOriginalIndices(start, offset);
7790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
7800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
7820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        protected void moveInUIThread(int from, int to) {
7830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            boolean setAsFullSpanAgain = mFullSpanItems.contains(from);
7840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            super.moveInUIThread(from, to);
7850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (setAsFullSpanAgain) {
7860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mFullSpanItems.add(to);
7870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7880a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
7890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
7900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        @Override
7910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        public void onBindViewHolder(TestViewHolder holder,
7920a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                int position) {
7930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (mSizeReference == null) {
7940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mSizeReference = mOrientation == OrientationHelper.HORIZONTAL ? mRecyclerViewWidth
7950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        / AVG_ITEM_PER_VIEW : mRecyclerViewHeight / AVG_ITEM_PER_VIEW;
7960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
7970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            super.onBindViewHolder(holder, position);
7980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            Item item = mItems.get(position);
7990a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
8000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    .getLayoutParams();
8010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
8024143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                ((StaggeredGridLayoutManager.LayoutParams) lp)
8034143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        .setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
8040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            } else {
8054143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                StaggeredGridLayoutManager.LayoutParams slp
8064143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        = (StaggeredGridLayoutManager.LayoutParams) mLayoutManager
8074143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar                        .generateDefaultLayoutParams();
8080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                holder.itemView.setLayoutParams(slp);
8090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
8100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lp = slp;
8110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
8120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
8130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (mOnBindCallback == null || mOnBindCallback.assignRandomSize()) {
8140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                final int minSize = mViewsHaveEqualSize ? mSizeReference :
8150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                        mSizeReference + 20 * (item.mId % 10);
8160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                if (mOrientation == OrientationHelper.HORIZONTAL) {
8170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    holder.itemView.setMinimumWidth(minSize);
8180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                } else {
8190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                    holder.itemView.setMinimumHeight(minSize);
8200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                }
8210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lp.topMargin = 3;
8220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lp.leftMargin = 5;
8230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lp.rightMargin = 7;
8240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                lp.bottomMargin = 9;
8250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
8260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
8270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            if (mOnBindCallback != null) {
8280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mOnBindCallback.onBoundItem(holder, position);
8290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            }
8300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        }
8310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
8320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar}
833