1d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
3d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
4d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * you may not use this file except in compliance with the License.
6d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * You may obtain a copy of the License at
7d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
8d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar *
10d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Unless required by applicable law or agreed to in writing, software
11d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * See the License for the specific language governing permissions and
14d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * limitations under the License.
15d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar */
16d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
18d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
19151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiriimport static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiriimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
22c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
23c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
24c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport static org.hamcrest.CoreMatchers.is;
26e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertEquals;
27b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport static org.junit.Assert.assertFalse;
28e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertNotNull;
29b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport static org.junit.Assert.assertNull;
30e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertSame;
319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport static org.junit.Assert.assertThat;
32e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertTrue;
330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.Color;
359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.drawable.ColorDrawable;
369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.drawable.StateListDrawable;
37fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiriimport android.os.Build;
389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.support.test.filters.LargeTest;
39fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiriimport android.support.test.filters.SdkSuppress;
408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log;
419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.util.StateSet;
42d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View;
43504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup;
44a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent;
45d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
46c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
47c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.AccessibilityDelegateCompat;
48c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.ViewCompat;
49c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
50e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.junit.Test;
51e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas
52d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList;
53d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List;
54b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport java.util.concurrent.CountDownLatch;
55b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport java.util.concurrent.TimeUnit;
56504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
57d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/**
608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}.
618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p>
628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation
638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling
648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing.
658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */
669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri@LargeTest
67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
69046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard    /**
70046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     * Tests that the LinearLayoutManager retains the focused element after multiple measure
71046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     * calls to the RecyclerView.  There was a bug where the focused view was lost when the soft
72046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     * keyboard opened.  This test simulates the measure/layout events triggered by the opening
73046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     * of the soft keyboard by making two calls to measure.  A simulation was done because using
74046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     * the soft keyboard in the test caused many issues on API levels 15, 17 and 19.
75046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard     */
76151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri    @Test
77046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard    public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable {
78046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
79046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Arrange.
80046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
81046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final RecyclerView recyclerView = inflateWrappedRV();
82046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
83151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        lp.height = WRAP_CONTENT;
84151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        lp.width = MATCH_PARENT;
85046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        recyclerView.setHasFixedSize(true);
86151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
87046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final FocusableAdapter focusableAdapter =
88046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                new FocusableAdapter(50);
89046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        recyclerView.setAdapter(focusableAdapter);
90151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
91046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
92046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        recyclerView.setLayoutManager(mLayoutManager);
93151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
94151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        mLayoutManager.expectLayouts(1);
95151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        mActivityRule.runOnUiThread(new Runnable() {
96151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            @Override
97151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            public void run() {
98046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                getActivity().getContainer().addView(recyclerView);
99151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            }
100151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        });
101046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.waitForLayout(3);
102046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
103046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        int width = recyclerView.getWidth();
104046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        int height = recyclerView.getHeight();
105046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final int widthMeasureSpec =
106046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
107046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final int fullHeightMeasureSpec =
108046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
109046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView
110046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // was previously laid out with the full height version.
111046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final int fullHeightMinusOneMeasureSpec =
112046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST);
113046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        final int halfHeightMeasureSpec =
114046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST);
115046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
116046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Act 1.
117046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
118046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        View toFocus = findLastFullyVisibleChild(recyclerView);
119046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        int focusIndex = recyclerView.getChildAdapterPosition(toFocus);
120046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
121046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        requestFocus(toFocus, false);
122046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
123046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.expectLayouts(1);
124151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        mActivityRule.runOnUiThread(new Runnable() {
125151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            @Override
126151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            public void run() {
127046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
128046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
129046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.layout(
130046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
131046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
132046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredWidth(),
133046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredHeight());
134151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            }
135151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        });
136046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.waitForLayout(3);
137151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
138046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Verify 1.
139151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
140151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
141151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri                toFocus.hasFocus(), is(true));
142151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        // Testing for partial visibility instead of full visibility since TextView calls
143151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
144151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        // containing the content area. This rect is guaranteed to be fully visible whereas a
145151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        // portion of TextView could be out of bounds.
146046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        assertThat("Child view at adapter pos " + focusIndex + " should be fully visible.",
147046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                isViewPartiallyInBound(recyclerView, toFocus), is(true));
148046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
149046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Act 2.
150046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
151046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.expectLayouts(1);
152046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mActivityRule.runOnUiThread(new Runnable() {
153151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            @Override
154046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard            public void run() {
155046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec);
156046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.layout(
157046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
158046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
159046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredWidth(),
160046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredHeight());
161151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            }
162151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        });
163046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.waitForLayout(3);
164046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
165046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Verify 2.
166046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
167151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
168151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri                toFocus.hasFocus(), is(true));
169151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
170046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                isViewPartiallyInBound(recyclerView, toFocus));
171046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
172046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Act 3.
173151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
174151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        // Now focus on the first fully visible EditText.
175046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        toFocus = findFirstFullyVisibleChild(recyclerView);
176046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        focusIndex = recyclerView.getChildAdapterPosition(toFocus);
177046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
178046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        requestFocus(toFocus, false);
179046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
180046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.expectLayouts(1);
181046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mActivityRule.runOnUiThread(new Runnable() {
182151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            @Override
183046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard            public void run() {
184046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
185046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
186046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                recyclerView.layout(
187046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
188046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        0,
189046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredWidth(),
190046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                        recyclerView.getMeasuredHeight());
191151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri            }
192151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        });
193046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        mLayoutManager.waitForLayout(3);
194046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
195046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard        // Assert 3.
196046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard
197151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
198046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                isViewPartiallyInBound(recyclerView, toFocus));
199151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri    }
200151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri
2010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
2029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void topUnfocusableViewsVisibility() throws Throwable {
2039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of child views that can be visible at any time.
2049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleChildCount = 5;
2059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveFocusablesCount = 2;
2069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveUnFocusablesCount = 18;
2079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final TestAdapter adapter = new TestAdapter(
2089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                consecutiveFocusablesCount + consecutiveUnFocusablesCount) {
2099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            RecyclerView mAttachedRv;
2109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
2128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
2139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
2149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // Good to have colors for debugging
2159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                StateListDrawable stl = new StateListDrawable();
2169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(new int[]{android.R.attr.state_focused},
2179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        new ColorDrawable(Color.RED));
2189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
2199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                //noinspection deprecation used to support kitkat tests
2209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                testViewHolder.itemView.setBackgroundDrawable(stl);
2219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                return testViewHolder;
2229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
2239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
2259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
2269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                mAttachedRv = recyclerView;
2279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
2289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
2308a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
2319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    int position) {
2329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                super.onBindViewHolder(holder, position);
2339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                if (position < consecutiveFocusablesCount) {
2349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(true);
2359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(true);
2369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                } else {
2379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(false);
2389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(false);
2399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                }
2409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // This height ensures that some portion of #visibleChildCount'th child is
2419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // off-bounds, creating more interesting test scenario.
2429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                holder.itemView.setMinimumHeight((mAttachedRv.getHeight()
2439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount);
2449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
2459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        };
2469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(VERTICAL, false, false).adapter(adapter).reverseLayout(true),
2479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                false);
2489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitForFirstLayout();
2499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
2519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 0;
2529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View newFocused = mRecyclerView.getChildAt(focusIndex);
2539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        requestFocus(newFocused, true);
2549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
2559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
2569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
2579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                toFocus.itemView.hasFocus(), is(true));
2589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the item (whether focusable or not) that just becomes fully
2609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // visible after focusSearch.
2619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int visibleIndex = 0;
2629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the above adapter position
2639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
2649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Navigate up through the focusable and unfocusable chunks. The focusable items should
2669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // become focused one by one until hitting the last focusable item, at which point,
2679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // unfocusable items should become visible on the screen until the currently focused item
2689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // stays on the screen.
2699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i = 0; i < adapter.getItemCount(); i++) {
2709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_UP, true);
2719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            // adapter position of the currently focused item.
2729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1));
2739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex);
2749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2,
2759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    (visibleIndex + 1));
2769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex);
2779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertThat("Child at position " + focusIndex + " should be focused",
2799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    toFocus.itemView.hasFocus(), is(true));
2809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
2819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, toFocus.itemView));
2829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.",
2839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
2849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
2859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
2869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
2889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void bottomUnfocusableViewsVisibility() throws Throwable {
2899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of child views that can be visible at any time.
2909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleChildCount = 5;
2919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveFocusablesCount = 2;
2929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveUnFocusablesCount = 18;
2939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final TestAdapter adapter = new TestAdapter(
2949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                consecutiveFocusablesCount + consecutiveUnFocusablesCount) {
2959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            RecyclerView mAttachedRv;
2969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
2979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
2988a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
2999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
3009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // Good to have colors for debugging
3019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                StateListDrawable stl = new StateListDrawable();
3029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(new int[]{android.R.attr.state_focused},
3039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        new ColorDrawable(Color.RED));
3049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
3059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                //noinspection deprecation used to support kitkat tests
3069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                testViewHolder.itemView.setBackgroundDrawable(stl);
3079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                return testViewHolder;
3089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
3099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
3119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
3129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                mAttachedRv = recyclerView;
3139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
3149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
3168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
3179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    int position) {
3189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                super.onBindViewHolder(holder, position);
3199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                if (position < consecutiveFocusablesCount) {
3209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(true);
3219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(true);
3229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                } else {
3239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(false);
3249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(false);
3259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                }
3269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // This height ensures that some portion of #visibleChildCount'th child is
3279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // off-bounds, creating more interesting test scenario.
3289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                holder.itemView.setMinimumHeight((mAttachedRv.getHeight()
3299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount);
3309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
3319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        };
3329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(VERTICAL, false, false).adapter(adapter), false);
3339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitForFirstLayout();
3349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
3369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 0;
3379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View newFocused = mRecyclerView.getChildAt(focusIndex);
3389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        requestFocus(newFocused, true);
3399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
3409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
3419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
3429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                toFocus.itemView.hasFocus(), is(true));
3439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the item (whether focusable or not) that just becomes fully
3459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // visible after focusSearch.
3469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int visibleIndex = 0;
3479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the above adapter position
3489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
3499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Navigate down through the focusable and unfocusable chunks. The focusable items should
3519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // become focused one by one until hitting the last focusable item, at which point,
3529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // unfocusable items should become visible on the screen until the currently focused item
3539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // stays on the screen.
3549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i = 0; i < adapter.getItemCount(); i++) {
3559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_DOWN, true);
3569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            // adapter position of the currently focused item.
3579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1));
3589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex);
3599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2,
3609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    (visibleIndex + 1));
3619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex);
3629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertThat("Child at position " + focusIndex + " should be focused",
3649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    toFocus.itemView.hasFocus(), is(true));
3659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
3669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, toFocus.itemView));
3679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.",
3689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
3699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
3709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
3719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
3739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void leftUnfocusableViewsVisibility() throws Throwable {
3749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of child views that can be visible at any time.
3759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleChildCount = 5;
3769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveFocusablesCount = 2;
3779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveUnFocusablesCount = 18;
3789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final TestAdapter adapter = new TestAdapter(
3799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                consecutiveFocusablesCount + consecutiveUnFocusablesCount) {
3809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            RecyclerView mAttachedRv;
3819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
3838a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
3849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
3859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // Good to have colors for debugging
3869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                StateListDrawable stl = new StateListDrawable();
3879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(new int[]{android.R.attr.state_focused},
3889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        new ColorDrawable(Color.RED));
3899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
3909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                //noinspection deprecation used to support kitkat tests
3919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                testViewHolder.itemView.setBackgroundDrawable(stl);
3929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                return testViewHolder;
3939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
3949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
3959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
3969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
3979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                mAttachedRv = recyclerView;
3989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
3999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
4018a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
4029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    int position) {
4039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                super.onBindViewHolder(holder, position);
4049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                if (position < consecutiveFocusablesCount) {
4059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(true);
4069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(true);
4079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                } else {
4089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(false);
4099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(false);
4109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                }
4119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // This width ensures that some portion of #visibleChildCount'th child is
4129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // off-bounds, creating more interesting test scenario.
4139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                holder.itemView.setMinimumWidth((mAttachedRv.getWidth()
4149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        + mAttachedRv.getWidth() / (2 * visibleChildCount)) / visibleChildCount);
4159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
4169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        };
4179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(HORIZONTAL, false, false).adapter(adapter).reverseLayout(true),
4189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                false);
4199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitForFirstLayout();
4209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
4229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 0;
4239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View newFocused = mRecyclerView.getChildAt(focusIndex);
4249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        requestFocus(newFocused, true);
4259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
4269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
4279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
4289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                toFocus.itemView.hasFocus(), is(true));
4299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the item (whether focusable or not) that just becomes fully
4319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // visible after focusSearch.
4329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int visibleIndex = 0;
4339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the above adapter position
4349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
4359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Navigate left through the focusable and unfocusable chunks. The focusable items should
4379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // become focused one by one until hitting the last focusable item, at which point,
4389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // unfocusable items should become visible on the screen until the currently focused item
4399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // stays on the screen.
4409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i = 0; i < adapter.getItemCount(); i++) {
4419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_LEFT, true);
4429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            // adapter position of the currently focused item.
4439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1));
4449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex);
4459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2,
4469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    (visibleIndex + 1));
4479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex);
4489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertThat("Child at position " + focusIndex + " should be focused",
4509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    toFocus.itemView.hasFocus(), is(true));
4519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
4529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, toFocus.itemView));
4539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.",
4549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
4559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
4569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
4579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
4599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    public void rightUnfocusableViewsVisibility() throws Throwable {
4609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The maximum number of child views that can be visible at any time.
4619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int visibleChildCount = 5;
4629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveFocusablesCount = 2;
4639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final int consecutiveUnFocusablesCount = 18;
4649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        final TestAdapter adapter = new TestAdapter(
4659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                consecutiveFocusablesCount + consecutiveUnFocusablesCount) {
4669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            RecyclerView mAttachedRv;
4679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
4698a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
4709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
4719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // Good to have colors for debugging
4729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                StateListDrawable stl = new StateListDrawable();
4739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(new int[]{android.R.attr.state_focused},
4749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        new ColorDrawable(Color.RED));
4759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
4769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                //noinspection deprecation used to support kitkat tests
4779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                testViewHolder.itemView.setBackgroundDrawable(stl);
4789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                return testViewHolder;
4799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
4809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
4829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
4839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                mAttachedRv = recyclerView;
4849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
4859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
4869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            @Override
4878a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
4889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    int position) {
4899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                super.onBindViewHolder(holder, position);
4909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                if (position < consecutiveFocusablesCount) {
4919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(true);
4929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(true);
4939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                } else {
4949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusable(false);
4959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    holder.itemView.setFocusableInTouchMode(false);
4969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                }
4979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // This width ensures that some portion of #visibleChildCount'th child is
4989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                // off-bounds, creating more interesting test scenario.
4999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                holder.itemView.setMinimumWidth((mAttachedRv.getWidth()
5009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                        + mAttachedRv.getWidth() / (2 * visibleChildCount)) / visibleChildCount);
5019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            }
5029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        };
5039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        setupByConfig(new Config(HORIZONTAL, false, false).adapter(adapter), false);
5049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        waitForFirstLayout();
5059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the currently focused item.
5079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int focusIndex = 0;
5089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        View newFocused = mRecyclerView.getChildAt(focusIndex);
5099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        requestFocus(newFocused, true);
5109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
5119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                focusIndex);
5129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
5139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                toFocus.itemView.hasFocus(), is(true));
5149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // adapter position of the item (whether focusable or not) that just becomes fully
5169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // visible after focusSearch.
5179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        int visibleIndex = 0;
5189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // The VH of the above adapter position
5199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        RecyclerView.ViewHolder toVisible = null;
5209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // Navigate right through the focusable and unfocusable chunks. The focusable items should
5229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // become focused one by one until hitting the last focusable item, at which point,
5239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // unfocusable items should become visible on the screen until the currently focused item
5249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        // stays on the screen.
5259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        for (int i = 0; i < adapter.getItemCount(); i++) {
5269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_RIGHT, true);
5279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            // adapter position of the currently focused item.
5289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1));
5299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex);
5309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2,
5319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    (visibleIndex + 1));
5329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex);
5339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
5349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertThat("Child at position " + focusIndex + " should be focused",
5359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    toFocus.itemView.hasFocus(), is(true));
5369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Focused child should be at least partially visible.",
5379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewPartiallyInBound(mRecyclerView, toFocus.itemView));
5389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri            assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.",
5399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri                    isViewFullyInBound(mRecyclerView, toVisible.itemView));
5409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri        }
5419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    }
5429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri
543fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    // Run this test on Jelly Bean and newer because clearFocus on API 15 will call
544fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    // requestFocus in ViewRootImpl when clearChildFocus is called. Whereas, in API 16 and above,
545fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    // this call is delayed until after onFocusChange callback is called. Thus on API 16+, there's a
546fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    // transient state of no child having focus during which onFocusChange is executed. This
547fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    // transient state does not exist on API 15-.
548fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
5499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri    @Test
550b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri    public void unfocusableScrollingWhenFocusCleared() throws Throwable {
551b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        // The maximum number of child views that can be visible at any time.
552b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final int visibleChildCount = 5;
553b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final int consecutiveFocusablesCount = 2;
554b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final int consecutiveUnFocusablesCount = 18;
555b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final TestAdapter adapter = new TestAdapter(
556b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                consecutiveFocusablesCount + consecutiveUnFocusablesCount) {
557b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            RecyclerView mAttachedRv;
558b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
559b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            @Override
5608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
561b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
562b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                // Good to have colors for debugging
563b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                StateListDrawable stl = new StateListDrawable();
564b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                stl.addState(new int[]{android.R.attr.state_focused},
565b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        new ColorDrawable(Color.RED));
566b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
567b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                //noinspection deprecation used to support kitkat tests
568b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                testViewHolder.itemView.setBackgroundDrawable(stl);
569b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                return testViewHolder;
570b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            }
571b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
572b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            @Override
573b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            public void onAttachedToRecyclerView(RecyclerView recyclerView) {
574b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                mAttachedRv = recyclerView;
575b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            }
576b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
577b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            @Override
5788a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
579b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    int position) {
580b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                super.onBindViewHolder(holder, position);
581b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                if (position < consecutiveFocusablesCount) {
582b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    holder.itemView.setFocusable(true);
583b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    holder.itemView.setFocusableInTouchMode(true);
584b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                } else {
585b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    holder.itemView.setFocusable(false);
586b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    holder.itemView.setFocusableInTouchMode(false);
587b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                }
588b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                // This height ensures that some portion of #visibleChildCount'th child is
589b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                // off-bounds, creating more interesting test scenario.
590b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                holder.itemView.setMinimumHeight((mAttachedRv.getHeight()
591b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount);
592b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            }
593b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        };
594b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        setupByConfig(new Config(VERTICAL, false, false).adapter(adapter), false);
595b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        waitForFirstLayout();
596b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
597b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        // adapter position of the currently focused item.
598b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        int focusIndex = 0;
599b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        View newFocused = mRecyclerView.getChildAt(focusIndex);
600b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        requestFocus(newFocused, true);
601b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition(
602b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                focusIndex);
603b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
604b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                toFocus.itemView.hasFocus(), is(true));
605b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
606b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final View nextView = focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_DOWN, true);
607b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        focusIndex++;
608b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        assertThat("Child at position " + focusIndex + " should be focused",
609b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView.hasFocus(),
610b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                is(true));
611b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        final CountDownLatch focusLatch = new CountDownLatch(1);
612b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        mActivityRule.runOnUiThread(new Runnable() {
613b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            @Override
614b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            public void run() {
615b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                nextView.setOnFocusChangeListener(new View.OnFocusChangeListener(){
616b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    @Override
617b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    public void onFocusChange(View v, boolean hasFocus) {
618b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        assertNull("Focus just got cleared and no children should be holding"
619046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard                                + " focus now.", mRecyclerView.getFocusedChild());
620b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        try {
621b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            // Calling focusSearch should be a no-op here since even though there
622b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            // are unfocusable views down to scroll to, none of RV's children hold
623b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            // focus at this stage.
624b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            View focusedChild  = focusSearch(v, View.FOCUS_DOWN, true);
625b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            assertNull("Calling focusSearch should be no-op when no children hold"
626b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                    + "focus", focusedChild);
627b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            // No scrolling should have happened, so any unfocusables that were
628b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            // invisible should still be invisible.
629b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            RecyclerView.ViewHolder unforcusablePartiallyVisibleChild =
630b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                    mRecyclerView.findViewHolderForAdapterPosition(
631b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                            visibleChildCount - 1);
632b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            assertFalse("Child view at adapter pos " + (visibleChildCount - 1)
633b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                            + " should not be fully visible.",
634b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                    isViewFullyInBound(mRecyclerView,
635b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                                            unforcusablePartiallyVisibleChild.itemView));
636b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        } catch (Throwable t) {
637b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                            postExceptionToInstrumentation(t);
638b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                        }
639b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                    }
640b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                });
641b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                nextView.clearFocus();
642b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                focusLatch.countDown();
643b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri            }
644b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        });
645b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        assertTrue(focusLatch.await(2, TimeUnit.SECONDS));
646b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri        assertThat("Child at position " + focusIndex + " should no longer be focused",
647b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView.hasFocus(),
648b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri                is(false));
649b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri    }
650b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri
651b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri    @Test
6520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItem() throws Throwable {
6531f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6541f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout(
6551f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                        false), 100, 0);
6561f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6571f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemReverse() throws Throwable {
6601f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6611f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout(true), 100,
6621f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                0);
6631f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6641f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemStackFromEnd() throws Throwable {
6671f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6681f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(false), 100,
6691f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                99);
6701f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6711f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemStackFromEndAndReverse() throws Throwable {
6741f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6751f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(true), 100,
6761f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                99);
6771f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6781f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemHorizontal() throws Throwable {
6811f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6821f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout(
6831f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                        false), 100, 0);
6841f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6851f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemReverseHorizontal() throws Throwable {
6881f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6891f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout(true),
6901f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                100, 0);
6911f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6921f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
6930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
6940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemStackFromEndHorizontal() throws Throwable {
6951f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
6961f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(false),
6971f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                100, 99);
6981f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
6991f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
7000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
7010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void removeAnchorItemStackFromEndAndReverseHorizontal() throws Throwable {
7021f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        removeAnchorItemTest(
7031f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(true), 100,
7041f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                99);
7051f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
7061f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
7071f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    /**
7081f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar     * This tests a regression where predictive animations were not working as expected when the
7091f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar     * first item is removed and there aren't any more items to add from that direction.
7101f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar     * First item refers to the default anchor item.
7111f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar     */
7121f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    public void removeAnchorItemTest(final Config config, int adapterSize,
7131f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            final int removePos) throws Throwable {
7141f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        config.adapter(new TestAdapter(adapterSize) {
7151f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            @Override
7168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
7171f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    int position) {
7181f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                super.onBindViewHolder(holder, position);
7191f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
7201f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                if (!(lp instanceof ViewGroup.MarginLayoutParams)) {
7211f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    lp = new ViewGroup.MarginLayoutParams(0, 0);
7221f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    holder.itemView.setLayoutParams(lp);
7231f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                }
7241f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
7251f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                final int maxSize;
7261f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                if (config.mOrientation == HORIZONTAL) {
7271f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    maxSize = mRecyclerView.getWidth();
728e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                    mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
7291f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                } else {
7301f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    maxSize = mRecyclerView.getHeight();
731e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas                    mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
7321f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                }
7331f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
7341f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                final int desiredSize;
7351f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                if (position == removePos) {
7361f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    // make it large
7371f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    desiredSize = maxSize / 4;
7381f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                } else {
7391f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    // make it small
7401f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    desiredSize = maxSize / 8;
7411f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                }
7421f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                if (config.mOrientation == HORIZONTAL) {
7431f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    mlp.width = desiredSize;
7441f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                } else {
7451f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    mlp.height = desiredSize;
7461f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                }
7471f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            }
7481f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        });
7491f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        setupByConfig(config, true);
7501f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        final int childCount = mLayoutManager.getChildCount();
7511f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        RecyclerView.ViewHolder toBeRemoved = null;
7521f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        List<RecyclerView.ViewHolder> toBeMoved = new ArrayList<RecyclerView.ViewHolder>();
7531f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        for (int i = 0; i < childCount; i++) {
7541f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            View child = mLayoutManager.getChildAt(i);
7551f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child);
7561f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            if (holder.getAdapterPosition() == removePos) {
7571f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                toBeRemoved = holder;
7581f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            } else {
7591f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                toBeMoved.add(holder);
7601f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            }
7611f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        }
7621f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertNotNull("test sanity", toBeRemoved);
7631f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertEquals("test sanity", childCount - 1, toBeMoved.size());
7641f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        LoggingItemAnimator loggingItemAnimator = new LoggingItemAnimator();
7651f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        mRecyclerView.setItemAnimator(loggingItemAnimator);
7661f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        loggingItemAnimator.reset();
7671f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        loggingItemAnimator.expectRunPendingAnimationsCall(1);
7681f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        mLayoutManager.expectLayouts(2);
7691f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        mTestAdapter.deleteAndNotify(removePos, 1);
7701f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        mLayoutManager.waitForLayout(1);
7711f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        loggingItemAnimator.waitForPendingAnimationsCall(2);
7721f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertTrue("removed child should receive remove animation",
7731f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                loggingItemAnimator.mRemoveVHs.contains(toBeRemoved));
7741f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        for (RecyclerView.ViewHolder vh : toBeMoved) {
7751f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            assertTrue("view holder should be in moved list",
7761f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    loggingItemAnimator.mMoveVHs.contains(vh));
7771f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        }
7781f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        List<RecyclerView.ViewHolder> newHolders = new ArrayList<RecyclerView.ViewHolder>();
7791f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
7801f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            View child = mLayoutManager.getChildAt(i);
7811f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child);
7821f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            if (toBeRemoved != holder && !toBeMoved.contains(holder)) {
7831f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                newHolders.add(holder);
7841f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            }
7851f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        }
7861f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertTrue("some new children should show up for the new space", newHolders.size() > 0);
7871f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertEquals("no items should receive animate add since they are not new", 0,
7881f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                loggingItemAnimator.mAddVHs.size());
7891f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        for (RecyclerView.ViewHolder holder : newHolders) {
7901f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar            assertTrue("new holder should receive a move animation",
7911f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                    loggingItemAnimator.mMoveVHs.contains(holder));
7921f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        }
7931f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar        assertTrue("control against adding too many children due to bad layout state preparation."
7941f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                        + " initial:" + childCount + ", current:" + mRecyclerView.getChildCount(),
7951f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar                mRecyclerView.getChildCount() <= childCount + 3 /*1 for removed view, 2 for its size*/);
7961f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar    }
7971f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar
79808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu    void waitOneCycle() throws Throwable {
79908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mActivityRule.runOnUiThread(new Runnable() {
80008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
80108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            public void run() {
80208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
80308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        });
80408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu    }
80508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
806b84e677c6a8709e68db54ecc0475038592277051Dake Gu    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
80708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu    @Test
80808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu    public void hiddenNoneRemoveViewAccessibility() throws Throwable {
80908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final Config config = new Config();
81008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        int adapterSize = 1000;
81108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final boolean[] firstItemSpecialSize = new boolean[] {false};
81208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        TestAdapter adapter = new TestAdapter(adapterSize) {
81308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
8148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull TestViewHolder holder,
81508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    int position) {
81608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                super.onBindViewHolder(holder, position);
81708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
81808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                if (!(lp instanceof ViewGroup.MarginLayoutParams)) {
81908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    lp = new ViewGroup.MarginLayoutParams(0, 0);
82008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    holder.itemView.setLayoutParams(lp);
82108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                }
82208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
82308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                final int maxSize;
82408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                if (config.mOrientation == HORIZONTAL) {
82508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    maxSize = mRecyclerView.getWidth();
82608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
82708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                } else {
82808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    maxSize = mRecyclerView.getHeight();
82908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
83008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                }
83108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
83208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                final int desiredSize;
83308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                if (position == 0 && firstItemSpecialSize[0]) {
83408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    desiredSize = maxSize / 3;
83508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                } else {
83608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    desiredSize = maxSize / 8;
83708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                }
83808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                if (config.mOrientation == HORIZONTAL) {
83908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    mlp.width = desiredSize;
84008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                } else {
84108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    mlp.height = desiredSize;
84208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                }
84308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
84408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
84508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
84608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            public void onBindViewHolder(TestViewHolder holder,
84708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                    int position, List<Object> payloads) {
84808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                onBindViewHolder(holder, position);
84908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
85008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        };
85108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        adapter.setHasStableIds(false);
85208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        config.adapter(adapter);
85308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        setupByConfig(config, true);
85408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final DummyItemAnimator itemAnimator = new DummyItemAnimator();
85508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mRecyclerView.setItemAnimator(itemAnimator);
85608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
85708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // push last item out by increasing first item's size
85808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final int childBeingPushOut = mLayoutManager.getChildCount() - 1;
85908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        RecyclerView.ViewHolder itemViewHolder = mRecyclerView
86008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                .findViewHolderForAdapterPosition(childBeingPushOut);
86108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final int originalAccessibility = ViewCompat.getImportantForAccessibility(
86208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                itemViewHolder.itemView);
86308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == originalAccessibility
86408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == originalAccessibility);
86508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
86608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        itemAnimator.expect(DummyItemAnimator.MOVE_START, 1);
86708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mActivityRule.runOnUiThread(new Runnable() {
86808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
86908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            public void run() {
87008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                firstItemSpecialSize[0] = true;
87108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                mTestAdapter.notifyItemChanged(0, "XXX");
87208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
87308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        });
87408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // wait till itemAnimator starts which will block itemView's accessibility
87508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        itemAnimator.waitFor(DummyItemAnimator.MOVE_START);
87608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // RV Changes accessiblity after onMoveStart, so wait one more cycle.
87708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        waitOneCycle();
87808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        assertTrue(itemAnimator.getMovesAnimations().contains(itemViewHolder));
87908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        assertEquals(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
88008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                ViewCompat.getImportantForAccessibility(itemViewHolder.itemView));
88108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
88208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // notify Change again to run predictive animation.
88308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mLayoutManager.expectLayouts(2);
88408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mActivityRule.runOnUiThread(new Runnable() {
88508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
88608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            public void run() {
88708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                mTestAdapter.notifyItemChanged(0, "XXX");
88808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
88908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        });
89008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mLayoutManager.waitForLayout(1);
89108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        mActivityRule.runOnUiThread(new Runnable() {
89208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            @Override
89308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            public void run() {
89408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                itemAnimator.endAnimations();
89508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu            }
89608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        });
89708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // scroll to the view being pushed out, it should get same view from cache as the item
89808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // in adapter does not change.
89908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        smoothScrollToPosition(childBeingPushOut);
90008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        RecyclerView.ViewHolder itemViewHolder2 = mRecyclerView
90108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                .findViewHolderForAdapterPosition(childBeingPushOut);
90208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        assertSame(itemViewHolder, itemViewHolder2);
90308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        // the important for accessibility should be reset to YES/AUTO:
90408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        final int newAccessibility = ViewCompat.getImportantForAccessibility(
90508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                itemViewHolder.itemView);
90608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu        assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == newAccessibility
90708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu                || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == newAccessibility);
90808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu    }
90908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu
9100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9112ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu    public void layoutFrozenBug70402422() throws Throwable {
9122ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        final Config config = new Config();
9132ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        TestAdapter adapter = new TestAdapter(2);
9142ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        adapter.setHasStableIds(false);
9152ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        config.adapter(adapter);
9162ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        setupByConfig(config, true);
9172ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        final DummyItemAnimator itemAnimator = new DummyItemAnimator();
9182ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mRecyclerView.setItemAnimator(itemAnimator);
9192ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu
9202ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        final View firstItemView = mRecyclerView
9212ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu                .findViewHolderForAdapterPosition(0).itemView;
9222ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu
9232ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        itemAnimator.expect(DummyItemAnimator.REMOVE_START, 1);
9242ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mTestAdapter.deleteAndNotify(1, 1);
9252ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        itemAnimator.waitFor(DummyItemAnimator.REMOVE_START);
9262ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu
9272ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mActivityRule.runOnUiThread(new Runnable() {
9282ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            @Override
9292ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            public void run() {
9302ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu                mRecyclerView.setLayoutFrozen(true);
9312ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            }
9322ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        });
9332ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        // requestLayout during item animation, which should be eaten by setLayoutFrozen(true)
9342ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mActivityRule.runOnUiThread(new Runnable() {
9352ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            @Override
9362ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            public void run() {
9372ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu                firstItemView.requestLayout();
9382ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            }
9392ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        });
9402ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        assertTrue(firstItemView.isLayoutRequested());
9412ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        assertFalse(mRecyclerView.isLayoutRequested());
9422ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mActivityRule.runOnUiThread(new Runnable() {
9432ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            @Override
9442ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            public void run() {
9452ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu                itemAnimator.endAnimations();
9462ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            }
9472ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        });
9482ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        // When setLayoutFrozen(false), the firstItemView should run a layout pass and clear
9492ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        // isLayoutRequested() flag.
9502ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mLayoutManager.expectLayouts(1);
9512ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mActivityRule.runOnUiThread(new Runnable() {
9522ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            @Override
9532ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            public void run() {
9542ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu                mRecyclerView.setLayoutFrozen(false);
9552ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu            }
9562ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        });
9572ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        mLayoutManager.waitForLayout(1);
9582ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        assertFalse(firstItemView.isLayoutRequested());
9592ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu        assertFalse(mRecyclerView.isLayoutRequested());
9602ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu    }
9612ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu
9622ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu    @Test
9630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepFocusOnRelayout() throws Throwable {
964310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
965310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        int center = (mLayoutManager.findLastVisibleItemPosition()
966310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                - mLayoutManager.findFirstVisibleItemPosition()) / 2;
967115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
968310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
969959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        requestFocus(vh.itemView, true);
970310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertTrue("view should have the focus", vh.itemView.hasFocus());
971310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        // add a bunch of items right before that view, make sure it keeps its position
972310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.expectLayouts(2);
973310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        final int childCountToAdd = mRecyclerView.getChildCount() * 2;
974310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mTestAdapter.addAndNotify(center, childCountToAdd);
975310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        center += childCountToAdd; // offset item
976310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForLayout(2);
977310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        mLayoutManager.waitForAnimationsToEnd(20);
978115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
979310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertNotNull("focused child should stay in layout", postVH);
980310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertSame("same view holder should be kept for unchanged child", vh, postVH);
981310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        assertEquals("focused child's screen position should stay unchanged", top,
982310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar                mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
983310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
984310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
9850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepFullFocusOnResize() throws Throwable {
987542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true);
988542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
989542f1260934df280985294eaef1ec8469863281fYigit Boyar
9900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepPartialFocusOnResize() throws Throwable {
992542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false);
993542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
994542f1260934df280985294eaef1ec8469863281fYigit Boyar
9950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
9960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepReverseFullFocusOnResize() throws Throwable {
997542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true);
998542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
999542f1260934df280985294eaef1ec8469863281fYigit Boyar
10000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepReversePartialFocusOnResize() throws Throwable {
1002542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false);
1003542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
1004542f1260934df280985294eaef1ec8469863281fYigit Boyar
10050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepStackFromEndFullFocusOnResize() throws Throwable {
1007542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true);
1008542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
1009542f1260934df280985294eaef1ec8469863281fYigit Boyar
10100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
10110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void keepStackFromEndPartialFocusOnResize() throws Throwable {
1012542f1260934df280985294eaef1ec8469863281fYigit Boyar        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false);
1013542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
1014542f1260934df280985294eaef1ec8469863281fYigit Boyar
1015542f1260934df280985294eaef1ec8469863281fYigit Boyar    public void keepFocusOnResizeTest(final Config config, boolean fullyVisible) throws Throwable {
1016542f1260934df280985294eaef1ec8469863281fYigit Boyar        setupByConfig(config, true);
1017542f1260934df280985294eaef1ec8469863281fYigit Boyar        final int targetPosition;
1018542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (config.mStackFromEnd) {
1019542f1260934df280985294eaef1ec8469863281fYigit Boyar            targetPosition = mLayoutManager.findFirstVisibleItemPosition();
1020542f1260934df280985294eaef1ec8469863281fYigit Boyar        } else {
1021542f1260934df280985294eaef1ec8469863281fYigit Boyar            targetPosition = mLayoutManager.findLastVisibleItemPosition();
1022542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1023542f1260934df280985294eaef1ec8469863281fYigit Boyar        final OrientationHelper helper = mLayoutManager.mOrientationHelper;
1024542f1260934df280985294eaef1ec8469863281fYigit Boyar        final RecyclerView.ViewHolder vh = mRecyclerView
1025542f1260934df280985294eaef1ec8469863281fYigit Boyar                .findViewHolderForLayoutPosition(targetPosition);
1026542f1260934df280985294eaef1ec8469863281fYigit Boyar
1027542f1260934df280985294eaef1ec8469863281fYigit Boyar        // scroll enough to offset the child
1028542f1260934df280985294eaef1ec8469863281fYigit Boyar        int startMargin = helper.getDecoratedStart(vh.itemView) -
1029542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getStartAfterPadding();
1030542f1260934df280985294eaef1ec8469863281fYigit Boyar        int endMargin = helper.getEndAfterPadding() -
1031542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedEnd(vh.itemView);
1032542f1260934df280985294eaef1ec8469863281fYigit Boyar        Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin);
1033959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        requestFocus(vh.itemView, true);
1034959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        assertTrue("view should gain the focus", vh.itemView.hasFocus());
1035542f1260934df280985294eaef1ec8469863281fYigit Boyar        // scroll enough to offset the child
1036542f1260934df280985294eaef1ec8469863281fYigit Boyar        startMargin = helper.getDecoratedStart(vh.itemView) -
1037542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getStartAfterPadding();
1038542f1260934df280985294eaef1ec8469863281fYigit Boyar        endMargin = helper.getEndAfterPadding() -
1039542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedEnd(vh.itemView);
1040542f1260934df280985294eaef1ec8469863281fYigit Boyar
1041542f1260934df280985294eaef1ec8469863281fYigit Boyar        Log.d(TAG, "start margin " + startMargin + " , end margin:" + endMargin);
1042542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("View should become fully visible", startMargin >= 0 && endMargin >= 0);
1043542f1260934df280985294eaef1ec8469863281fYigit Boyar
1044542f1260934df280985294eaef1ec8469863281fYigit Boyar        int expectedOffset = 0;
1045542f1260934df280985294eaef1ec8469863281fYigit Boyar        boolean offsetAtStart = false;
1046542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (!fullyVisible) {
1047542f1260934df280985294eaef1ec8469863281fYigit Boyar            // move it a bit such that it is no more fully visible
1048542f1260934df280985294eaef1ec8469863281fYigit Boyar            final int childSize = helper
1049542f1260934df280985294eaef1ec8469863281fYigit Boyar                    .getDecoratedMeasurement(vh.itemView);
1050542f1260934df280985294eaef1ec8469863281fYigit Boyar            expectedOffset = childSize / 3;
1051542f1260934df280985294eaef1ec8469863281fYigit Boyar            if (startMargin < endMargin) {
1052542f1260934df280985294eaef1ec8469863281fYigit Boyar                scrollBy(expectedOffset);
1053542f1260934df280985294eaef1ec8469863281fYigit Boyar                offsetAtStart = true;
1054542f1260934df280985294eaef1ec8469863281fYigit Boyar            } else {
1055542f1260934df280985294eaef1ec8469863281fYigit Boyar                scrollBy(-expectedOffset);
1056542f1260934df280985294eaef1ec8469863281fYigit Boyar                offsetAtStart = false;
1057542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
1058542f1260934df280985294eaef1ec8469863281fYigit Boyar            startMargin = helper.getDecoratedStart(vh.itemView) -
1059542f1260934df280985294eaef1ec8469863281fYigit Boyar                    helper.getStartAfterPadding();
1060542f1260934df280985294eaef1ec8469863281fYigit Boyar            endMargin = helper.getEndAfterPadding() -
1061542f1260934df280985294eaef1ec8469863281fYigit Boyar                    helper.getDecoratedEnd(vh.itemView);
1062542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("test sanity, view should not be fully visible", startMargin < 0
1063542f1260934df280985294eaef1ec8469863281fYigit Boyar                    || endMargin < 0);
1064542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1065542f1260934df280985294eaef1ec8469863281fYigit Boyar
1066542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.expectLayouts(1);
106742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
1068542f1260934df280985294eaef1ec8469863281fYigit Boyar            @Override
1069542f1260934df280985294eaef1ec8469863281fYigit Boyar            public void run() {
1070542f1260934df280985294eaef1ec8469863281fYigit Boyar                final ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams();
1071542f1260934df280985294eaef1ec8469863281fYigit Boyar                if (config.mOrientation == HORIZONTAL) {
1072542f1260934df280985294eaef1ec8469863281fYigit Boyar                    layoutParams.width = mRecyclerView.getWidth() / 2;
1073542f1260934df280985294eaef1ec8469863281fYigit Boyar                } else {
1074542f1260934df280985294eaef1ec8469863281fYigit Boyar                    layoutParams.height = mRecyclerView.getHeight() / 2;
1075542f1260934df280985294eaef1ec8469863281fYigit Boyar                }
1076542f1260934df280985294eaef1ec8469863281fYigit Boyar                mRecyclerView.setLayoutParams(layoutParams);
1077542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
1078542f1260934df280985294eaef1ec8469863281fYigit Boyar        });
1079542f1260934df280985294eaef1ec8469863281fYigit Boyar        Thread.sleep(100);
1080542f1260934df280985294eaef1ec8469863281fYigit Boyar        // add a bunch of items right before that view, make sure it keeps its position
1081542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.waitForLayout(2);
1082542f1260934df280985294eaef1ec8469863281fYigit Boyar        mLayoutManager.waitForAnimationsToEnd(20);
1083542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("view should preserve the focus", vh.itemView.hasFocus());
1084542f1260934df280985294eaef1ec8469863281fYigit Boyar        final RecyclerView.ViewHolder postVH = mRecyclerView
1085542f1260934df280985294eaef1ec8469863281fYigit Boyar                .findViewHolderForLayoutPosition(targetPosition);
1086542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertNotNull("focused child should stay in layout", postVH);
1087542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertSame("same view holder should be kept for unchanged child", vh, postVH);
1088542f1260934df280985294eaef1ec8469863281fYigit Boyar        View focused = postVH.itemView;
1089542f1260934df280985294eaef1ec8469863281fYigit Boyar
1090542f1260934df280985294eaef1ec8469863281fYigit Boyar        startMargin = helper.getDecoratedStart(focused) - helper.getStartAfterPadding();
1091542f1260934df280985294eaef1ec8469863281fYigit Boyar        endMargin = helper.getEndAfterPadding() - helper.getDecoratedEnd(focused);
1092542f1260934df280985294eaef1ec8469863281fYigit Boyar
1093542f1260934df280985294eaef1ec8469863281fYigit Boyar        assertTrue("focused child should be somewhat visible",
1094542f1260934df280985294eaef1ec8469863281fYigit Boyar                helper.getDecoratedStart(focused) < helper.getEndAfterPadding()
1095542f1260934df280985294eaef1ec8469863281fYigit Boyar                        && helper.getDecoratedEnd(focused) > helper.getStartAfterPadding());
1096542f1260934df280985294eaef1ec8469863281fYigit Boyar        if (fullyVisible) {
1097542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("focused child end should stay fully visible",
1098542f1260934df280985294eaef1ec8469863281fYigit Boyar                    endMargin >= 0);
1099542f1260934df280985294eaef1ec8469863281fYigit Boyar            assertTrue("focused child start should stay fully visible",
1100542f1260934df280985294eaef1ec8469863281fYigit Boyar                    startMargin >= 0);
1101542f1260934df280985294eaef1ec8469863281fYigit Boyar        } else {
1102542f1260934df280985294eaef1ec8469863281fYigit Boyar            if (offsetAtStart) {
1103542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("start should preserve its offset", startMargin < 0);
1104542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("end should be visible", endMargin >= 0);
1105542f1260934df280985294eaef1ec8469863281fYigit Boyar            } else {
1106542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("end should preserve its offset", endMargin < 0);
1107542f1260934df280985294eaef1ec8469863281fYigit Boyar                assertTrue("start should be visible", startMargin >= 0);
1108542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
1109542f1260934df280985294eaef1ec8469863281fYigit Boyar        }
1110542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
1111542f1260934df280985294eaef1ec8469863281fYigit Boyar
11120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
11130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void scrollToPositionWithPredictive() throws Throwable {
11146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
11156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
11166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(3, 20);
11176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
11186e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
11196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar                LinearLayoutManager.INVALID_OFFSET);
11206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        removeRecyclerView();
11216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
11226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar    }
11236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
11240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
11250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void recycleDuringAnimations() throws Throwable {
1126504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final AtomicInteger childCount = new AtomicInteger(0);
1127504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final TestAdapter adapter = new TestAdapter(300) {
1128504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
11298a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
1130504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    int viewType) {
1131504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final int cnt = childCount.incrementAndGet();
1132504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
1133504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
1134504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder);
1135504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
1136504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return testViewHolder;
1137504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
1138504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
1139310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        setupByConfig(new Config(VERTICAL, false, false).itemCount(300)
1140504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .adapter(adapter), true);
1141504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1142504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
1143504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
1144504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public void putRecycledView(RecyclerView.ViewHolder scrap) {
1145504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                super.putRecycledView(scrap);
1146504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                int cnt = childCount.decrementAndGet();
1147504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (DEBUG) {
1148504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap);
1149504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
1150504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
1151504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1152504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            @Override
1153504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            public RecyclerView.ViewHolder getRecycledView(int viewType) {
1154504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
1155504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                if (recycledView != null) {
1156504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    final int cnt = childCount.incrementAndGet();
1157504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    if (DEBUG) {
1158504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView);
1159504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
1160504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                }
1161504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                return recycledView;
1162504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            }
1163504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        };
1164504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500);
1165504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        mRecyclerView.setRecycledViewPool(pool);
1166504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1167504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1168504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now keep adding children to trigger more children being created etc.
1169504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
1170504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(15, 1);
1171504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(15);
1172504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
1173504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
1174504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
1175504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
1176504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
1177504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1178504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        // now trigger lots of add again, followed by a scroll to position
1179504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        for (int i = 0; i < 100; i ++) {
1180504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            adapter.addAndNotify(5 + (i % 3) * 3, 1);
1181504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            Thread.sleep(25);
1182504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
118367ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard
118467ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard        final AtomicInteger lastVisiblePosition = new AtomicInteger();
118567ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard        mActivityRule.runOnUiThread(new Runnable() {
118667ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard            @Override
118767ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard            public void run() {
118867ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard                lastVisiblePosition.set(mLayoutManager.findLastVisibleItemPosition());
118967ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard            }
119067ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard        });
119167ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard
119267ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard        smoothScrollToPosition(lastVisiblePosition.get() + 20);
1193504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        waitForAnimations(2);
1194504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
1195504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        assertEquals("Children count should add up", childCount.get(),
1196504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
1197504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
1198504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
1199d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar
12000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void dontRecycleChildrenOnDetach() throws Throwable {
120249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(false), true);
120342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
120449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
120549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
120649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
120734a33c72e21528b39fe39ca1048ebb9c0d617647Yigit Boyar                ((ViewGroup)mRecyclerView.getParent()).removeView(mRecyclerView);
120849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("No views are recycled", recyclerSize,
120949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
121049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
121149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
121249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
121349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
12140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void recycleChildrenOnDetach() throws Throwable {
121649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        setupByConfig(new Config().recycleChildrenOnDetach(true), true);
121749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        final int childCount = mLayoutManager.getChildCount();
121842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
121949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            @Override
122049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            public void run() {
122149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
122249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
122349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mTestAdapter.getItemViewType(0), recyclerSize + childCount);
122434a33c72e21528b39fe39ca1048ebb9c0d617647Yigit Boyar                ((ViewGroup)mRecyclerView.getParent()).removeView(mRecyclerView);
122549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                assertEquals("All children should be recycled", childCount + recyclerSize,
122649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar                        mRecyclerView.mRecycler.getRecycledViewPool().size());
122749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar            }
122849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar        });
122949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar    }
123049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar
12310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void scrollAndClear() throws Throwable {
12333d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        setupByConfig(new Config(), true);
12343d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
12353d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        assertTrue("Children not laid out", mLayoutManager.collectChildCoordinates().size() > 0);
12363d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
12373d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        mLayoutManager.expectLayouts(1);
123842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
12393d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            @Override
12403d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            public void run() {
12413d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mLayoutManager.scrollToPositionWithOffset(1, 0);
12423d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mTestAdapter.clearOnUIThread();
12433d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            }
12443d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        });
12453d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        mLayoutManager.waitForLayout(2);
12463d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
12473d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        assertEquals("Remaining children", 0, mLayoutManager.collectChildCoordinates().size());
12483d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev    }
12493d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
12503d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
12510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
12520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void accessibilityPositions() throws Throwable {
1253a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        setupByConfig(new Config(VERTICAL, false, false), true);
1254a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityDelegateCompat delegateCompat = mRecyclerView
1255a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                .getCompatAccessibilityDelegate();
1256a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        final AccessibilityEvent event = AccessibilityEvent.obtain();
125742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
1258a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            @Override
1259a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            public void run() {
1260a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event);
1261a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar            }
1262a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        });
1263a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have first position",
126414d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas                event.getFromIndex(),
1265a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findFirstVisibleItemPosition());
1266a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar        assertEquals("result should have last position",
126714d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas                event.getToIndex(),
1268a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar                mLayoutManager.findLastVisibleItemPosition());
1269a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar    }
1270d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar}
1271