12d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
32d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar *
42d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
52d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * you may not use this file except in compliance with the License.
62d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * You may obtain a copy of the License at
72d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar *
82d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
92d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar *
102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * Unless required by applicable law or agreed to in writing, software
112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * See the License for the specific language governing permissions and
142d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * limitations under the License.
152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar */
162d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
182d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
20ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager
21bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        .GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
22ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
23ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.HORIZONTAL;
24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams;
25bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
2620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapardimport static org.hamcrest.CoreMatchers.equalTo;
27bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertEquals;
28bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertFalse;
29bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertNotNull;
30bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertNull;
31bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertSame;
32bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertThat;
33bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertTrue;
340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
35f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.Color;
362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.graphics.Rect;
37f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.drawable.ColorDrawable;
38f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.drawable.StateListDrawable;
392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.os.Parcel;
402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.os.Parcelable;
4135232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikasimport android.support.test.filters.LargeTest;
42bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.text.TextUtils;
432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.util.Log;
44f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.util.StateSet;
452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.view.View;
462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.view.ViewGroup;
47a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent;
48bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.widget.EditText;
49bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.widget.FrameLayout;
50bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
51c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
52c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.AccessibilityDelegateCompat;
53c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
54bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport org.hamcrest.CoreMatchers;
55f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyarimport org.hamcrest.MatcherAssert;
56bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport org.junit.Test;
572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
5842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyarimport java.util.HashMap;
592d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport java.util.Map;
602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport java.util.UUID;
612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
6235232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikas@LargeTest
630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpublic class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManagerTest {
6420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
6520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    @Test
6620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    public void layout_rvHasPaddingChildIsMatchParentVertical_childrenAreInsideParent()
6720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            throws Throwable {
6820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(VERTICAL, false);
6920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    }
7020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
7120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    @Test
7220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    public void layout_rvHasPaddingChildIsMatchParentHorizontal_childrenAreInsideParent()
7320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            throws Throwable {
7420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(HORIZONTAL, false);
7520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    }
7620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
7720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    @Test
7820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    public void layout_rvHasPaddingChildIsMatchParentVerticalFullSpan_childrenAreInsideParent()
7920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            throws Throwable {
8020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(VERTICAL, true);
8120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    }
8220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
8320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    @Test
8420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    public void layout_rvHasPaddingChildIsMatchParentHorizontalFullSpan_childrenAreInsideParent()
8520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            throws Throwable {
8620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(HORIZONTAL, true);
8720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    }
8820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
8920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    private void layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(
9020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            final int orientation, final boolean fullSpan)
9120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            throws Throwable {
9220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
9320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        setupByConfig(new Config(orientation, false, 1, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
9420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                new GridTestAdapter(10, orientation) {
9520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
968a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull
9720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    @Override
988a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(
998a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                            @NonNull ViewGroup parent, int viewType) {
10020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        View view = new View(parent.getContext());
10120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        StaggeredGridLayoutManager.LayoutParams layoutParams =
10220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                                new StaggeredGridLayoutManager.LayoutParams(
10320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                                        ViewGroup.LayoutParams.MATCH_PARENT,
10420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                                        ViewGroup.LayoutParams.MATCH_PARENT);
10520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        layoutParams.setFullSpan(fullSpan);
10620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        view.setLayoutParams(layoutParams);
10720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        return new TestViewHolder(view);
10820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    }
10920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
11020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    @Override
1118a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
11220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        // No actual binding needed, but we need to override this to prevent default
11320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                        // behavior of GridTestAdapter.
11420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    }
11520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                });
11620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        mRecyclerView.setPadding(1, 2, 3, 4);
11720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
11820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        waitFirstLayout();
11920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
12020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        mActivityRule.runOnUiThread(new Runnable() {
12120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            @Override
12220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            public void run() {
12320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                int childDimension;
12420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                int recyclerViewDimensionMinusPadding;
12520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                if (orientation == VERTICAL) {
12620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    childDimension = mRecyclerView.getChildAt(0).getHeight();
12720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    recyclerViewDimensionMinusPadding = mRecyclerView.getHeight()
12820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                            - mRecyclerView.getPaddingTop()
12920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                            - mRecyclerView.getPaddingBottom();
13020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                } else {
13120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    childDimension = mRecyclerView.getChildAt(0).getWidth();
13220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                    recyclerViewDimensionMinusPadding = mRecyclerView.getWidth()
13320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                            - mRecyclerView.getPaddingLeft()
13420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                            - mRecyclerView.getPaddingRight();
13520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                }
13620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard                assertThat(childDimension, equalTo(recyclerViewDimensionMinusPadding));
13720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard            }
13820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard        });
13920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard    }
14020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard
141a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar    @Test
142a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar    public void forceLayoutOnDetach() throws Throwable {
143a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
144a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        waitFirstLayout();
145a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        assertFalse("test sanity", mRecyclerView.isLayoutRequested());
14642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
147a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar            @Override
148a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar            public void run() {
149a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar                mLayoutManager.onDetachedFromWindow(mRecyclerView, mRecyclerView.mRecycler);
150a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar            }
151a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        });
152a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        assertTrue(mRecyclerView.isLayoutRequested());
153a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar    }
1548a11e6829c522aa1efcc903afa4c01d337082eabChris Craik
1550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
1560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void areAllStartsTheSame() throws Throwable {
157f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300));
158f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        waitFirstLayout();
159f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(100);
160f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(1);
161f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.deleteAndNotify(0, 2);
1629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mLayoutManager.waitForLayout(2000);
163f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(0);
164f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual());
165f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
166f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
1680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void areAllEndsTheSame() throws Throwable {
169f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300));
170f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        waitFirstLayout();
171f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(100);
172f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(1);
173f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.deleteAndNotify(0, 2);
174f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.waitForLayout(2);
175f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(0);
176f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual());
177f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
178f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
180f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar    public void getPositionsBeforeInitialization() throws Throwable {
181f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
182f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar        int[] positions = mLayoutManager.findFirstCompletelyVisibleItemPositions(null);
183f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar        MatcherAssert.assertThat(positions,
184f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar                CoreMatchers.is(new int[]{RecyclerView.NO_POSITION, RecyclerView.NO_POSITION,
185f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar                        RecyclerView.NO_POSITION}));
186f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar    }
187f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar
188f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar    @Test
1890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void findLastInUnevenDistribution() throws Throwable {
1906490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
1916490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                .itemCount(5));
192e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mAdapter.mOnBindCallback = new OnBindCallback() {
1936490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            @Override
1946490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            void onBoundItem(TestViewHolder vh, int position) {
1956490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
1966490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                if (position == 1) {
1976490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                    lp.height = mRecyclerView.getHeight() - 10;
1986490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                } else {
1996490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                    lp.height = 5;
2006490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                }
201e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                vh.itemView.setMinimumHeight(0);
2026490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            }
2036490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        };
20452a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar        waitFirstLayout();
2056490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        int[] into = new int[2];
2066490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        mLayoutManager.findFirstCompletelyVisibleItemPositions(into);
2076490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("first completely visible item from span 0 should be 0", 0, into[0]);
2086490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("first completely visible item from span 1 should be 1", 1, into[1]);
2096490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        mLayoutManager.findLastCompletelyVisibleItemPositions(into);
2106490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("last completely visible item from span 0 should be 4", 4, into[0]);
2116490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("last completely visible item from span 1 should be 1", 1, into[1]);
2126490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("first fully visible child should be at position",
2136490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                0, mRecyclerView.getChildViewHolder(mLayoutManager.
214e5874e666791a58d21eed482fb90e917445da873Chris Craik                        findFirstVisibleItemClosestToStart(true)).getPosition());
2156490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("last fully visible child should be at position",
2166490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                4, mRecyclerView.getChildViewHolder(mLayoutManager.
217e5874e666791a58d21eed482fb90e917445da873Chris Craik                        findFirstVisibleItemClosestToEnd(true)).getPosition());
2186490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar
2196490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("first visible child should be at position",
2206490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                0, mRecyclerView.getChildViewHolder(mLayoutManager.
221e5874e666791a58d21eed482fb90e917445da873Chris Craik                        findFirstVisibleItemClosestToStart(false)).getPosition());
2226490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        assertEquals("last visible child should be at position",
2236490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar                4, mRecyclerView.getChildViewHolder(mLayoutManager.
224e5874e666791a58d21eed482fb90e917445da873Chris Craik                        findFirstVisibleItemClosestToEnd(false)).getPosition());
2256490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar
22642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar    }
22742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar
2280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void customWidthInHorizontal() throws Throwable {
23042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        customSizeInScrollDirectionTest(
23142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
23242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar    }
23342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar
2340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void customHeightInVertical() throws Throwable {
23642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        customSizeInScrollDirectionTest(
23742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
23842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar    }
23942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar
24042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar    public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
24142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        setupByConfig(config);
24242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        final Map<View, Integer> sizeMap = new HashMap<View, Integer>();
243e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mAdapter.mOnBindCallback = new OnBindCallback() {
24442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            @Override
24542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            void onBoundItem(TestViewHolder vh, int position) {
24642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                final ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams();
24742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                final int size = 1 + position * 5;
24842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                if (config.mOrientation == HORIZONTAL) {
24942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                    layoutParams.width = size;
25042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                } else {
25142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                    layoutParams.height = size;
25242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                }
25342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                sizeMap.put(vh.itemView, size);
25442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                if (position == 3) {
25542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                    getLp(vh.itemView).setFullSpan(true);
25642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                }
25742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            }
25842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar
25942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            @Override
26042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            boolean assignRandomSize() {
26142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                return false;
26242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            }
26342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        };
26442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        waitFirstLayout();
26542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        assertTrue("[test sanity] some views should be laid out", sizeMap.size() > 0);
26642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
26742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            View child = mRecyclerView.getChildAt(i);
26842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
26942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                    : child.getHeight();
27042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar            assertEquals("child " + i + " should have the size specified in its layout params",
27142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                    sizeMap.get(child).intValue(), size);
27242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        }
27352a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar        checkForMainThreadException();
27452a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar    }
27552a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar
2760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void gapHandlingWhenItemMovesToTop() throws Throwable {
278ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        gapHandlingWhenItemMovesToTopTest();
279ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar    }
280ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
2810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void gapHandlingWhenItemMovesToTopWithFullSpan() throws Throwable {
283ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        gapHandlingWhenItemMovesToTopTest(0);
284ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar    }
285ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
2860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void gapHandlingWhenItemMovesToTopWithFullSpan2() throws Throwable {
288ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        gapHandlingWhenItemMovesToTopTest(1);
289ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar    }
290ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
291ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar    public void gapHandlingWhenItemMovesToTopTest(int... fullSpanIndices) throws Throwable {
292ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        Config config = new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
293ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        config.itemCount(3);
294ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        setupByConfig(config);
295e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mAdapter.mOnBindCallback = new OnBindCallback() {
296ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            @Override
297ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            void onBoundItem(TestViewHolder vh, int position) {
298ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            }
299ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
300ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            @Override
301ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            boolean assignRandomSize() {
302ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                return false;
303ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            }
304ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        };
305ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        for (int i : fullSpanIndices) {
306ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            mAdapter.mFullSpanItems.add(i);
307ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        }
308ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        waitFirstLayout();
309ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.expectLayouts(1);
310ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mAdapter.moveItem(1, 0, true);
311ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.waitForLayout(2);
312ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        final Map<Item, Rect> desiredPositions = mLayoutManager.collectChildCoordinates();
313ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        // move back.
314ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.expectLayouts(1);
315ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mAdapter.moveItem(0, 1, true);
316ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.waitForLayout(2);
317ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.expectLayouts(2);
318ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mAdapter.moveAndNotify(1, 0);
319ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        mLayoutManager.waitForLayout(2);
320ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        Thread.sleep(1000);
321ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        getInstrumentation().waitForIdleSync();
322ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        checkForMainThreadException();
323ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        // item should be positioned properly
324ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        assertRectSetsEqual("final position after a move", desiredPositions,
325ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                mLayoutManager.collectChildCoordinates());
326ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
327ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar    }
328ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
3290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
3300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void focusSearchFailureUp() throws Throwable {
331f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        focusSearchFailure(false);
332f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    }
333f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
3340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
3350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void focusSearchFailureDown() throws Throwable {
336f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        focusSearchFailure(true);
337f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    }
338f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
339bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar    @Test
340bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar    public void focusSearchFailureFromSubChild() throws Throwable {
341bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
342bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                new GridTestAdapter(1000, VERTICAL) {
343bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
3448a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull
345bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                    @Override
3468a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(
3478a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                            @NonNull ViewGroup parent, int viewType) {
348bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        FrameLayout fl = new FrameLayout(parent.getContext());
349bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        EditText editText = new EditText(parent.getContext());
350bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        fl.addView(editText);
351bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        editText.setEllipsize(TextUtils.TruncateAt.END);
352bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        return new TestViewHolder(fl);
353bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                    }
354bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
355bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                    @Override
3568a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
357bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        Item item = mItems.get(position);
35826039a32f6f04a92fe0450074dea21dd38258265Yigit Boyar                        holder.mBoundItem = item;
359bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        ((EditText) ((FrameLayout) holder.itemView).getChildAt(0)).setText(
360bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                                item.mText + " (" + item.mId + ")");
3619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        // Good to have colors for debugging
3629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        StateListDrawable stl = new StateListDrawable();
3639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(new int[]{android.R.attr.state_focused},
3649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                new ColorDrawable(Color.RED));
3659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
3669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        //noinspection deprecation using this for kitkat tests
3679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        holder.itemView.setBackgroundDrawable(stl);
3689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (mOnBindCallback != null) {
3699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            mOnBindCallback.onBoundItem(holder, position);
3709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
371bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                    }
372bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                });
373bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri        mLayoutManager.expectLayouts(1);
374bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri        setRecyclerView(mRecyclerView);
375bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri        mLayoutManager.waitForLayout(10);
376bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri        getInstrumentation().waitForIdleSync();
377bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        ViewGroup lastChild = (ViewGroup) mRecyclerView.getChildAt(
378bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                mRecyclerView.getChildCount() - 1);
379bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        RecyclerView.ViewHolder lastViewHolder = mRecyclerView.getChildViewHolder(lastChild);
380bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        View subChildToFocus = lastChild.getChildAt(0);
381959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        requestFocus(subChildToFocus, true);
382bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        assertThat("test sanity", subChildToFocus.isFocused(), CoreMatchers.is(true));
383bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        focusSearch(subChildToFocus, View.FOCUS_FORWARD);
384959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        waitForIdleScroll(mRecyclerView);
385bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        checkForMainThreadException();
386bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        View focusedChild = mRecyclerView.getFocusedChild();
387bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        if (focusedChild == subChildToFocus.getParent()) {
388bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar            focusSearch(focusedChild, View.FOCUS_FORWARD);
389959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            waitForIdleScroll(mRecyclerView);
390bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar            focusedChild = mRecyclerView.getFocusedChild();
391bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        }
392bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        RecyclerView.ViewHolder containingViewHolder = mRecyclerView.findContainingViewHolder(
393bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                focusedChild);
394bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar        assertTrue("new focused view should have a larger position "
395bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        + lastViewHolder.getAdapterPosition() + " vs "
396bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                        + containingViewHolder.getAdapterPosition(),
397bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar                lastViewHolder.getAdapterPosition() < containingViewHolder.getAdapterPosition());
398bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar    }
399bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
400f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    public void focusSearchFailure(boolean scrollDown) throws Throwable {
401f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        int focusDir = scrollDown ? View.FOCUS_DOWN : View.FOCUS_UP;
402f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        setupByConfig(new Config(VERTICAL, !scrollDown, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
403f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                , new GridTestAdapter(31, 1) {
404f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    RecyclerView mAttachedRv;
405bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar
406f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    @Override
4078a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
408f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                            int viewType) {
409f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
410f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        testViewHolder.itemView.setFocusable(true);
411f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        testViewHolder.itemView.setFocusableInTouchMode(true);
412f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        // Good to have colors for debugging
413f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        StateListDrawable stl = new StateListDrawable();
414f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        stl.addState(new int[]{android.R.attr.state_focused},
415f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                                new ColorDrawable(Color.RED));
416f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
4179152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar                        //noinspection deprecation used to support kitkat tests
4189152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar                        testViewHolder.itemView.setBackgroundDrawable(stl);
419f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        return testViewHolder;
420f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    }
421f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
422f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    @Override
423f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
424f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        mAttachedRv = recyclerView;
425f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    }
426f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
427f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    @Override
4288a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder,
429f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                            int position) {
430f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        super.onBindViewHolder(holder, position);
431f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                        holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3);
432f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    }
433f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                });
434f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        /**
435f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 0  1  2
436f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 3  4  5
437f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 6  7  8
438f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 9  10 11
439f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 12 13 14
440f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 15 16 17
441f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 18 18 18
442f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 19
443f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 20 20 20
444f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 21 22
445f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 23 23 23
446f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 24 25 26
447f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 27 28 29
448f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         * 30
449f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar         */
450f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        mAdapter.mFullSpanItems.add(18);
451f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        mAdapter.mFullSpanItems.add(20);
452f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        mAdapter.mFullSpanItems.add(23);
453f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        waitFirstLayout();
454f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        View viewToFocus = mRecyclerView.findViewHolderForAdapterPosition(1).itemView;
455959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        assertTrue(requestFocus(viewToFocus, true));
456f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        assertSame(viewToFocus, mRecyclerView.getFocusedChild());
457f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        int pos = 1;
458f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        View focusedView = viewToFocus;
459f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        while (pos < 16) {
460f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusSearchAndWaitForScroll(focusedView, focusDir);
461f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusedView = mRecyclerView.getFocusedChild();
462f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            assertEquals(pos + 3,
463f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
464f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            pos += 3;
465f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        }
466f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        for (int i : new int[]{18, 19, 20, 21, 23, 24}) {
467f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusSearchAndWaitForScroll(focusedView, focusDir);
468f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusedView = mRecyclerView.getFocusedChild();
469f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
470f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        }
471f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        // now move right
472f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        focusSearch(focusedView, View.FOCUS_RIGHT);
473959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        waitForIdleScroll(mRecyclerView);
474f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        focusedView = mRecyclerView.getFocusedChild();
475f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        assertEquals(25, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
476f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        for (int i : new int[]{28, 30}) {
477f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusSearchAndWaitForScroll(focusedView, focusDir);
478f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            focusedView = mRecyclerView.getFocusedChild();
479f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
480f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        }
481f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    }
482f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
483f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    private void focusSearchAndWaitForScroll(View focused, int dir) throws Throwable {
484f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        focusSearch(focused, dir);
485959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        waitForIdleScroll(mRecyclerView);
486f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    }
487f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
4889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
4899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void topUnfocusableViewsVisibility() throws Throwable {
4909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of rows that can be fully in-bounds of RV.
4919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleRowCount = 5;
4929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int spanCount = 3;
4939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int lastFocusableIndex = 6;
4949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(VERTICAL, true, spanCount, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
4969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                new GridTestAdapter(18, 1) {
4979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    RecyclerView mAttachedRv;
4989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
5008a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
5019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int viewType) {
5029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
5039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusable(true);
5049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusableInTouchMode(true);
5059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        // Good to have colors for debugging
5069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        StateListDrawable stl = new StateListDrawable();
5079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(new int[]{android.R.attr.state_focused},
5089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                new ColorDrawable(Color.RED));
5099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
5109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        //noinspection deprecation used to support kitkat tests
5119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setBackgroundDrawable(stl);
5129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        return testViewHolder;
5139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
5149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
5169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
5179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        mAttachedRv = recyclerView;
5189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
5199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
5218a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder,
5229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int position) {
5239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        super.onBindViewHolder(holder, position);
5249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
5259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                .getLayoutParams();
5269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position <= lastFocusableIndex) {
5279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(true);
5289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(true);
5299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        } else {
5309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(false);
5319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(false);
5329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
5339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / visibleRowCount);
5349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.topMargin = 0;
5359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.leftMargin = 0;
5369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.rightMargin = 0;
5379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.bottomMargin = 0;
5389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position == 11) {
5399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            lp.bottomMargin = 9;
5409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
5419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
5429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                });
5439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        /**
5459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         *
5469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 15 16 17
5479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 12 13 14
5489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 11 11 11
5499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 9 10
5509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 8 8 8
5519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 7
5529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 6 6 6
5539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 3 4 5
5549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 0 1 2
5559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         */
5569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(6);
5579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(8);
5589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(11);
5599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitFirstLayout();
5609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
5639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 1;
5649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
5659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
5669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View viewToFocus = toFocus.itemView;
5679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertTrue(requestFocus(viewToFocus, true));
5689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertSame(viewToFocus, mRecyclerView.getFocusedChild());
5699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the unfocusable item that just became fully visible after focusSearch.
5719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
5729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View focusedView = viewToFocus;
5749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int actualFocusIndex = -1;
5759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // First, scroll until the last focusable row.
5769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{4, 6}) {
5779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP);
5789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
5799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
5809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
5819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, i, actualFocusIndex);
5829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
5839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Further scroll up in order to make the unfocusable rows visible. This process should
5859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // continue until the currently focused item is still visible. The focused item should not
5869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // change in this loop.
5879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{9, 11, 11, 11}) {
5889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP);
5899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
5909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
5919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
5929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should not be changed, whereas it's now at "
5949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, 6, actualFocusIndex);
5959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
5969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, focusedView));
5979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + i + " should be fully visible.",
5989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
5999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
6009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
6019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
6039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void bottomUnfocusableViewsVisibility() throws Throwable {
6049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of rows that can be fully in-bounds of RV.
6059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleRowCount = 5;
6069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int spanCount = 3;
6079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int lastFocusableIndex = 6;
6089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(VERTICAL, false, spanCount, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
6109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                new GridTestAdapter(18, 1) {
6119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    RecyclerView mAttachedRv;
6129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
6148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
6159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int viewType) {
6169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
6179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusable(true);
6189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusableInTouchMode(true);
6199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        // Good to have colors for debugging
6209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        StateListDrawable stl = new StateListDrawable();
6219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(new int[]{android.R.attr.state_focused},
6229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                new ColorDrawable(Color.RED));
6239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
6249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        //noinspection deprecation used to support kitkat tests
6259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setBackgroundDrawable(stl);
6269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        return testViewHolder;
6279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
6289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
6309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
6319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        mAttachedRv = recyclerView;
6329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
6339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
6358a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder,
6369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int position) {
6379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        super.onBindViewHolder(holder, position);
6389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
6399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                .getLayoutParams();
6409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position <= lastFocusableIndex) {
6419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(true);
6429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(true);
6439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        } else {
6449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(false);
6459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(false);
6469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
6479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / visibleRowCount);
6489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.topMargin = 0;
6499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.leftMargin = 0;
6509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.rightMargin = 0;
6519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.bottomMargin = 0;
6529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position == 11) {
6539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            lp.topMargin = 9;
6549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
6559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
6569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                });
6579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        /**
6599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 0 1 2
6609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 3 4 5
6619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 6 6 6
6629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 7
6639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 8 8 8
6649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 9 10
6659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 11 11 11
6669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 12 13 14
6679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 15 16 17
6689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         */
6699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(6);
6709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(8);
6719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(11);
6729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitFirstLayout();
6739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
6769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 1;
6779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
6789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
6799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View viewToFocus = toFocus.itemView;
6809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertTrue(requestFocus(viewToFocus, true));
6819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertSame(viewToFocus, mRecyclerView.getFocusedChild());
6829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the unfocusable item that just became fully visible after focusSearch.
6849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
6859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View focusedView = viewToFocus;
6879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int actualFocusIndex = -1;
6889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // First, scroll until the last focusable row.
6899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{4, 6}) {
6909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN);
6919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
6929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
6939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
6949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, i, actualFocusIndex);
6959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
6969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
6979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Further scroll down in order to make the unfocusable rows visible. This process should
6989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // continue until the currently focused item is still visible. The focused item should not
6999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // change in this loop.
7009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{9, 11, 11, 11}) {
7019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN);
7029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
7039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
7049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
7059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should not be changed, whereas it's now at "
7079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, 6, actualFocusIndex);
7089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
7099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, focusedView));
7109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + i + " should be fully visible.",
7119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
7129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
7139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
7149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
7169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void leftUnfocusableViewsVisibility() throws Throwable {
7179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of columns that can be fully in-bounds of RV.
7189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleColCount = 5;
7199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int spanCount = 3;
7209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int lastFocusableIndex = 6;
7219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Reverse layout so that views are placed from right to left.
7239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(HORIZONTAL, true, spanCount,
7249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
7259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                new GridTestAdapter(18, 1) {
7269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    RecyclerView mAttachedRv;
7279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
7298a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
7309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int viewType) {
7319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
7329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusable(true);
7339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusableInTouchMode(true);
7349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        // Good to have colors for debugging
7359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        StateListDrawable stl = new StateListDrawable();
7369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(new int[]{android.R.attr.state_focused},
7379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                new ColorDrawable(Color.RED));
7389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
7399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        //noinspection deprecation used to support kitkat tests
7409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setBackgroundDrawable(stl);
7419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        return testViewHolder;
7429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
7439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
7459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
7469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        mAttachedRv = recyclerView;
7479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
7489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
7508a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder,
7519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int position) {
7529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        super.onBindViewHolder(holder, position);
7539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
7549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                .getLayoutParams();
7559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position <= lastFocusableIndex) {
7569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(true);
7579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(true);
7589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        } else {
7599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(false);
7609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(false);
7619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
7629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        holder.itemView.setMinimumWidth(mAttachedRv.getWidth() / visibleColCount);
7639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.topMargin = 0;
7649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.leftMargin = 0;
7659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.rightMargin = 0;
7669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.bottomMargin = 0;
7679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position == 11) {
7689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            lp.rightMargin = 9;
7699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
7709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
7719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                });
7729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        /**
7749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 15 12 11 9  8 7 6 3 0
7759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 16 13 11 10 8   6 4 1
7769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 17 14 11    8   6 5 2
7779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         */
7789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(6);
7799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(8);
7809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(11);
7819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitFirstLayout();
7829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
7859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 1;
7869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
7879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
7889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View viewToFocus = toFocus.itemView;
7899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertTrue(requestFocus(viewToFocus, true));
7909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertSame(viewToFocus, mRecyclerView.getFocusedChild());
7919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the unfocusable item that just became fully visible after focusSearch.
7939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
7949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
7959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View focusedView = viewToFocus;
7969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int actualFocusIndex = -1;
7979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // First, scroll until the last focusable column.
7989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{4, 6}) {
7999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT);
8009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
8019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
8029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
8039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, i, actualFocusIndex);
8049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
8059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Further scroll left in order to make the unfocusable columns visible. This process should
8079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // continue until the currently focused item is still visible. The focused item should not
8089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // change in this loop.
8099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{9, 11, 11, 11}) {
8109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT);
8119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
8129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
8139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
8149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should not be changed, whereas it's now at "
8169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, 6, actualFocusIndex);
8179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
8189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, focusedView));
8199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + i + " should be fully visible.",
8209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
8219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
8229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
8239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
8259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void rightUnfocusableViewsVisibility() throws Throwable {
8269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of columns that can be fully in-bounds of RV.
8279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleColCount = 5;
8289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int spanCount = 3;
8299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int lastFocusableIndex = 6;
8309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(HORIZONTAL, false, spanCount,
8329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
8339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                new GridTestAdapter(18, 1) {
8349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    RecyclerView mAttachedRv;
8359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
8378a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
8389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int viewType) {
8399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
8409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusable(true);
8419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setFocusableInTouchMode(true);
8429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        // Good to have colors for debugging
8439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        StateListDrawable stl = new StateListDrawable();
8449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(new int[]{android.R.attr.state_focused},
8459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                new ColorDrawable(Color.RED));
8469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
8479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        //noinspection deprecation used to support kitkat tests
8489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        testViewHolder.itemView.setBackgroundDrawable(stl);
8499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        return testViewHolder;
8509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
8519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
8539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
8549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        mAttachedRv = recyclerView;
8559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
8569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    @Override
8588a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    public void onBindViewHolder(@NonNull TestViewHolder holder,
8599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            int position) {
8609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        super.onBindViewHolder(holder, position);
8619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
8629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                                .getLayoutParams();
8639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position <= lastFocusableIndex) {
8649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(true);
8659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(true);
8669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        } else {
8679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusable(false);
8689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            holder.itemView.setFocusableInTouchMode(false);
8699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
8709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        holder.itemView.setMinimumWidth(mAttachedRv.getWidth() / visibleColCount);
8719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.topMargin = 0;
8729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.leftMargin = 0;
8739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.rightMargin = 0;
8749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        lp.bottomMargin = 0;
8759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        if (position == 11) {
8769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                            lp.leftMargin = 9;
8779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        }
8789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    }
8799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                });
8809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        /**
8829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 0 3 6 7 8 9  11 12 15
8839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 1 4 6   8 10 11 13 16
8849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         * 2 5 6   8    11 14 17
8859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri         */
8869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(6);
8879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(8);
8889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        mAdapter.mFullSpanItems.add(11);
8899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitFirstLayout();
8909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
8929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
8939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 1;
8949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
8959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
8969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View viewToFocus = toFocus.itemView;
8979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertTrue(requestFocus(viewToFocus, true));
8989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertSame(viewToFocus, mRecyclerView.getFocusedChild());
8999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
9009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the unfocusable item that just became fully visible after focusSearch.
9019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
9029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
9039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View focusedView = viewToFocus;
9049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int actualFocusIndex = -1;
9059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // First, scroll until the last focusable column.
9069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{4, 6}) {
9079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT);
9089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
9099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
9109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should be at adapter position " + i + " whereas it's at "
9119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, i, actualFocusIndex);
9129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
9139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
9149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Further scroll right in order to make the unfocusable rows visible. This process should
9159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // continue until the currently focused item is still visible. The focused item should not
9169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // change in this loop.
9179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i : new int[]{9, 11, 11, 11}) {
9189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT);
9199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusedView = mRecyclerView.getFocusedChild();
9209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition();
9219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(i);
9229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
9239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertEquals("Focused view should not be changed, whereas it's now at "
9249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    + actualFocusIndex, 6, actualFocusIndex);
9259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
9269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, focusedView));
9279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + i + " should be fully visible.",
9289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
9299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
9309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
931ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar
9320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void scrollToPositionWithPredictive() throws Throwable {
9346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
9356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
9366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
9376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                LinearLayoutManager.INVALID_OFFSET);
9386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
9396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(9, 20);
9406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
9416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
9426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
9436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
9446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
9456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
9466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throws Throwable {
9476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        setupByConfig(new Config(StaggeredGridLayoutManager.VERTICAL,
9486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                false, 3, StaggeredGridLayoutManager.GAP_HANDLING_NONE));
9496e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        waitFirstLayout();
9506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
9516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
9526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
953d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                RecyclerView rv = mLayoutManager.mRecyclerView;
9546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                if (state.isPreLayout()) {
9556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertEquals("pending scroll position should still be pending",
9566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                            scrollPosition, mLayoutManager.mPendingScrollPosition);
9576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
9586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("pending scroll position offset should still be pending",
9596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
9606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
9616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } else {
962115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    RecyclerView.ViewHolder vh = rv.findViewHolderForLayoutPosition(scrollPosition);
9636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    assertNotNull("scroll to position should work", vh);
9646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
9656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        assertEquals("scroll offset should be applied properly",
9666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getPaddingTop() + scrollOffset
9676e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                        + ((RecyclerView.LayoutParams) vh.itemView
96842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar                                        .getLayoutParams()).topMargin,
9696e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                mLayoutManager.getDecoratedTop(vh.itemView));
9706e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
9716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
9726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
9736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        };
9746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.expectLayouts(2);
97542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
9766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            @Override
9776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            public void run() {
9786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                try {
9796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    mAdapter.addAndNotify(0, 1);
9806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
9816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPosition(scrollPosition);
9826e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    } else {
9836e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
9846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                                scrollOffset);
9856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    }
9866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
9876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                } catch (Throwable throwable) {
9886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                    throwable.printStackTrace();
9896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                }
9906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
9916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            }
9926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        });
9936e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        mLayoutManager.waitForLayout(2);
9946e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        checkForMainThreadException();
9956e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
9966e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
9970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void moveGapHandling() throws Throwable {
999f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        Config config = new Config().spanCount(2).itemCount(40);
1000f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        setupByConfig(config);
1001f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        waitFirstLayout();
1002f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(2);
1003f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.moveAndNotify(4, 1);
1004f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.waitForLayout(2);
1005f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix());
1006f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
1007f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
10080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void updateAfterFullSpan() throws Throwable {
1010f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        updateAfterFullSpanGapHandlingTest(0);
1011f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
1012f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
10130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void updateAfterFullSpan2() throws Throwable {
1015f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        updateAfterFullSpanGapHandlingTest(20);
1016f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
1017f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
10180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void temporaryGapHandling() throws Throwable {
1020f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        int fullSpanIndex = 200;
1021f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        setupByConfig(new Config().spanCount(2).itemCount(500));
1022f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.mFullSpanItems.add(fullSpanIndex);
1023f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        waitFirstLayout();
1024e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        smoothScrollToPosition(fullSpanIndex + 200);// go far away
1025e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNull("test sanity. full span item should not be visible",
1026e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                mRecyclerView.findViewHolderForAdapterPosition(fullSpanIndex));
1027f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(1);
1028f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
1029f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.waitForLayout(1);
1030f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(0);
1031f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(1);
1032e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        smoothScrollToPosition(fullSpanIndex + 2 * (AVG_ITEM_PER_VIEW - 1));
1033e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        String log = mLayoutManager.layoutToString("post gap");
1034f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.assertNoLayout("if an interim gap is fixed, it should not cause a "
1035e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                + "relayout " + log, 2);
1036f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
1037e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNotNull("full span item should be there:\n" + log, fullSpan);
1038f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
1039e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNotNull("next view should be there\n" + log, view1);
1040f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
1041e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNotNull("+2 view should be there\n" + log, view2);
1042f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1043f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
1044f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
1045f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("view 1 span index", 0, lp1.getSpanIndex());
1046f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("view 2 span index", 1, lp2.getSpanIndex());
1047f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("no gap between span and view 1",
1048f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
1049f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
1050f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("no gap between span and view 2",
1051f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
1052f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
1053f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
1054f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1055f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    public void updateAfterFullSpanGapHandlingTest(int fullSpanIndex) throws Throwable {
1056f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        setupByConfig(new Config().spanCount(2).itemCount(100));
1057f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.mFullSpanItems.add(fullSpanIndex);
1058f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        waitFirstLayout();
1059f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(fullSpanIndex + 30);
1060f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.expectLayouts(1);
1061f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
1062f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        mLayoutManager.waitForLayout(1);
1063f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        smoothScrollToPosition(fullSpanIndex);
1064f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        // give it some time to fix the gap
1065f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        Thread.sleep(500);
1066f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
1067f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1068f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
1069f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
1070f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
1071f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
1072f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
1073f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("view 1 span index", 0, lp1.getSpanIndex());
1074f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("view 2 span index", 1, lp2.getSpanIndex());
1075f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("no gap between span and view 1",
1076f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
1077f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
1078f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar        assertEquals("no gap between span and view 2",
1079f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
1080f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
1081f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar    }
1082f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar
10830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void innerGapHandling() throws Throwable {
10852d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
10862d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
10872d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
10882d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
10892d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    public void innerGapHandlingTest(int strategy) throws Throwable {
10902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Config config = new Config().spanCount(3).itemCount(500);
10912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        setupByConfig(config);
10922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.setGapStrategy(strategy);
10932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.mFullSpanItems.add(100);
10942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.mFullSpanItems.add(104);
10952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.mViewsHaveEqualSize = true;
1096e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mAdapter.mOnBindCallback = new OnBindCallback() {
1097e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            @Override
1098e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            void onBoundItem(TestViewHolder vh, int position) {
1099e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
1100e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            }
1101e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
1102e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            @Override
1103e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            void onCreatedViewHolder(TestViewHolder vh) {
1104e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                super.onCreatedViewHolder(vh);
1105e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                //make sure we have enough views
1106e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                mAdapter.mSizeReference = mRecyclerView.getHeight() / 5;
1107e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            }
1108e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        };
11092d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        waitFirstLayout();
11102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(1);
11112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        scrollToPosition(400);
11122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.waitForLayout(2);
1113e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        View view400 = mLayoutManager.findViewByPosition(400);
1114e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNotNull("test sanity, scrollToPos should succeed", view400);
1115e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertTrue("test sanity, view should be visible top",
1116e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view400) >=
1117e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                        mLayoutManager.mPrimaryOrientation.getStartAfterPadding());
1118e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertTrue("test sanity, view should be visible bottom",
1119e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(view400) <=
1120e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar                        mLayoutManager.mPrimaryOrientation.getEndAfterPadding());
11212d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(2);
11222d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.addAndNotify(101, 1);
11232d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.waitForLayout(2);
1124e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        checkForMainThreadException();
11252d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        if (strategy == GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
11262d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mLayoutManager.expectLayouts(1);
11272d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
11282d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // state
11292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // now smooth scroll to 99 to trigger a layout around 100
1130e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mLayoutManager.validateChildren();
11312d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        smoothScrollToPosition(99);
11322d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        switch (strategy) {
11332d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            case GAP_HANDLING_NONE:
11342d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                assertSpans("gap handling:" + Config.gapStrategyName(strategy), new int[]{100, 0},
11352d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                        new int[]{101, 2}, new int[]{102, 0}, new int[]{103, 1}, new int[]{104, 2},
11362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                        new int[]{105, 0});
11372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                break;
11382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
11392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mLayoutManager.waitForLayout(2);
11402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                assertSpans("swap items between spans", new int[]{100, 0}, new int[]{101, 0},
11412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                        new int[]{102, 1}, new int[]{103, 2}, new int[]{104, 0}, new int[]{105, 0});
11422d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                break;
11432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
11442d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
11452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
11462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
11470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
11480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void fullSizeSpans() throws Throwable {
11492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Config config = new Config().spanCount(5).itemCount(30);
11502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        setupByConfig(config);
11512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.mFullSpanItems.add(3);
11522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        waitFirstLayout();
11532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertSpans("Testing full size span", new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 2},
11542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                new int[]{3, 0}, new int[]{4, 0}, new int[]{5, 1}, new int[]{6, 2},
11552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                new int[]{7, 3}, new int[]{8, 4});
11562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
11572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
11582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    void assertSpans(String msg, int[]... childSpanTuples) {
1159e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        msg = msg + mLayoutManager.layoutToString("\n\n");
11602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        for (int i = 0; i < childSpanTuples.length; i++) {
11612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            assertSpan(msg, childSpanTuples[i][0], childSpanTuples[i][1]);
11622d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
11632d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
11642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
11652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    void assertSpan(String msg, int childPosition, int expectedSpan) {
11662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        View view = mLayoutManager.findViewByPosition(childPosition);
1167e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        assertNotNull(msg + " view at position " + childPosition + " should exists", view);
11682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertEquals(msg + "[child:" + childPosition + "]", expectedSpan,
11692d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                getLp(view).mSpan.mIndex);
11702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
11712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
11720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
11730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void partialSpanInvalidation() throws Throwable {
1174d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        Config config = new Config().spanCount(5).itemCount(100);
1175d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        setupByConfig(config);
1176d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        for (int i = 20; i < mAdapter.getItemCount(); i += 20) {
1177d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            mAdapter.mFullSpanItems.add(i);
1178d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
1179d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        waitFirstLayout();
1180d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        smoothScrollToPosition(50);
1181d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        int prevSpanId = mLayoutManager.mLazySpanLookup.mData[30];
1182d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        mAdapter.changeAndNotify(15, 2);
1183d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        Thread.sleep(200);
1184d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        assertEquals("Invalidation should happen within full span item boundaries", prevSpanId,
1185d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                mLayoutManager.mLazySpanLookup.mData[30]);
1186d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        assertEquals("item in invalidated range should have clear span id",
1187d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
1188d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        smoothScrollToPosition(85);
1189d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        int[] prevSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 62, 85);
1190d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        mAdapter.deleteAndNotify(55, 2);
1191d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        Thread.sleep(200);
1192d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        assertEquals("item in invalidated range should have clear span id",
1193d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
1194d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        int[] newSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 60, 83);
1195d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        assertSpanAssignmentEquality("valid spans should be shifted for deleted item", prevSpans,
1196d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                newSpans, 0, 0, newSpans.length);
1197d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar    }
1198d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar
1199d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar    // Same as Arrays.copyOfRange but for API 7
1200d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar    private int[] copyOfRange(int[] original, int from, int to) {
1201d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        int newLength = to - from;
120242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        if (newLength < 0) {
1203d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            throw new IllegalArgumentException(from + " > " + to);
120442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar        }
1205d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        int[] copy = new int[newLength];
1206d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        System.arraycopy(original, from, copy, 0,
1207d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar                Math.min(original.length - from, newLength));
1208d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        return copy;
1209d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar    }
1210d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar
12110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void spanReassignmentsOnItemChange() throws Throwable {
12132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Config config = new Config().spanCount(5);
12142d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        setupByConfig(config);
12152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        waitFirstLayout();
12162d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        smoothScrollToPosition(mAdapter.getItemCount() / 2);
12172d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final int changePosition = mAdapter.getItemCount() / 4;
12182d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(1);
12199152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar        if (RecyclerView.POST_UPDATES_ON_ANIMATION) {
12209152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar            mAdapter.changeAndNotify(changePosition, 1);
12219152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar            mLayoutManager.assertNoLayout("no layout should happen when an invisible child is "
12229152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar                    + "updated", 1);
12239152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar        } else {
12249152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar            mAdapter.changeAndNotify(changePosition, 1);
12259152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar            mLayoutManager.waitForLayout(1);
12269152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar        }
12279152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar
12282d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // delete an item before visible area
12292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(0)) - 2;
1230ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        assertTrue("test sanity", deletedPosition >= 0);
12312d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
12322d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        if (DEBUG) {
12332d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            Log.d(TAG, "before:");
12342d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (Map.Entry<Item, Rect> entry : before.entrySet()) {
12352d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                Log.d(TAG, entry.getKey().mAdapterIndex + ":" + entry.getValue());
12362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
12372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
12382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(1);
12392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.deleteAndNotify(deletedPosition, 1);
12402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.waitForLayout(2);
12412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertRectSetsEqual(config + " when an item towards the head of the list is deleted, it "
1242333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar                        + "should not affect the layout if it is not visible", before,
1243333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar                mLayoutManager.collectChildCoordinates()
1244333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar        );
12452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(2));
12462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(1);
12472d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mAdapter.deleteAndNotify(deletedPosition, 1);
12482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.waitForLayout(2);
12492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertRectSetsNotEqual(config + " when a visible item is deleted, it should affect the "
12502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                + "layout", before, mLayoutManager.collectChildCoordinates());
12512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
12522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
12532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start1, int start2,
12542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            int length) {
12552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        for (int i = 0; i < length; i++) {
12562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            assertEquals(msg + " ind1:" + (start1 + i) + ", ind2:" + (start2 + i), set1[start1 + i],
12572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    set2[start2 + i]);
12582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
12592d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
12602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
12610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void spanCountChangeOnRestoreSavedState() throws Throwable {
12636939444416c6eb0114bc1216fb0f683055bef135Yigit Boyar        Config config = new Config(HORIZONTAL, true, 5, GAP_HANDLING_NONE).itemCount(50);
12642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        setupByConfig(config);
12652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        waitFirstLayout();
12662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
12672d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int beforeChildCount = mLayoutManager.getChildCount();
12682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Parcelable savedState = mRecyclerView.onSaveInstanceState();
12692d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // we append a suffix to the parcelable to test out of bounds
12702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        String parcelSuffix = UUID.randomUUID().toString();
12712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Parcel parcel = Parcel.obtain();
12722d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        savedState.writeToParcel(parcel, 0);
12732d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        parcel.writeString(parcelSuffix);
12742d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        removeRecyclerView();
12752d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // reset for reading
12762d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        parcel.setDataPosition(0);
12772d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // re-create
12782d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
12792d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        removeRecyclerView();
12802d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
12812d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        RecyclerView restored = new RecyclerView(getActivity());
12822d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
1283b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar        mLayoutManager.setReverseLayout(config.mReverseLayout);
1284b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar        mLayoutManager.setGapStrategy(config.mGapStrategy);
12852d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        restored.setLayoutManager(mLayoutManager);
12862d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        // use the same adapter for Rect matching
12872d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        restored.setAdapter(mAdapter);
12882d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        restored.onRestoreInstanceState(savedState);
12892d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.setSpanCount(1);
12902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.expectLayouts(1);
12912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        setRecyclerView(restored);
12922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        mLayoutManager.waitForLayout(2);
12932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertEquals("on saved state, reverse layout should be preserved",
12942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                config.mReverseLayout, mLayoutManager.getReverseLayout());
12952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertEquals("on saved state, orientation should be preserved",
12962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                config.mOrientation, mLayoutManager.getOrientation());
1297b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar        assertEquals("after setting new span count, layout manager should keep new value",
12982d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                1, mLayoutManager.getSpanCount());
12992d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertEquals("on saved state, gap strategy should be preserved",
13002d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                config.mGapStrategy, mLayoutManager.getGapStrategy());
13012d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        assertTrue("when span count is dramatically changed after restore, # of child views "
13022d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                + "should change", beforeChildCount > mLayoutManager.getChildCount());
13036939444416c6eb0114bc1216fb0f683055bef135Yigit Boyar        // make sure SGLM can layout all children. is some span info is leaked, this would crash
13042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        smoothScrollToPosition(mAdapter.getItemCount() - 1);
13052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
1306e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
13070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
13080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void scrollAndClear() throws Throwable {
13090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        setupByConfig(new Config());
13100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        waitFirstLayout();
13112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
13120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertTrue("Children not laid out", mLayoutManager.collectChildCoordinates().size() > 0);
13132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
13140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.expectLayouts(1);
131542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
13160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            @Override
13170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            public void run() {
13180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mLayoutManager.scrollToPositionWithOffset(1, 0);
13190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                mAdapter.clearOnUIThread();
13202d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
13210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        });
13220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        mLayoutManager.waitForLayout(2);
13230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
13240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertEquals("Remaining children", 0, mLayoutManager.collectChildCoordinates().size());
13252d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar    }
13262d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
13270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
13280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void accessibilityPositions() throws Throwable {
13290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE));
13300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        waitFirstLayout();
13310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final AccessibilityDelegateCompat delegateCompat = mRecyclerView
13320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                .getCompatAccessibilityDelegate();
13330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final AccessibilityEvent event = AccessibilityEvent.obtain();
133442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
13356490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            @Override
13366490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            public void run() {
13370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event);
13386490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar            }
13396490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar        });
13400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int start = mRecyclerView
13410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                .getChildLayoutPosition(
1342e5874e666791a58d21eed482fb90e917445da873Chris Craik                        mLayoutManager.findFirstVisibleItemClosestToStart(false));
13430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        final int end = mRecyclerView
13440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                .getChildLayoutPosition(
1345e5874e666791a58d21eed482fb90e917445da873Chris Craik                        mLayoutManager.findFirstVisibleItemClosestToEnd(false));
13460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertEquals("first item position should match",
134714d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas                Math.min(start, end), event.getFromIndex());
13480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        assertEquals("last item position should match",
134914d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas                Math.max(start, end), event.getToIndex());
13503d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
13513d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev    }
135249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar}
1353