/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v17.leanback.widget; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Parcelable; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.filters.SdkSuppress; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.support.v17.leanback.test.R; import android.support.v17.leanback.testutils.PollingCheck; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerViewAccessibilityDelegate; import android.text.Selection; import android.text.Spannable; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @LargeTest @RunWith(AndroidJUnit4.class) public class GridWidgetTest { private static final float DELTA = 1f; private static final boolean HUMAN_DELAY = false; private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000; private static final int WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS = 2000; private static final int WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS = 6000; protected ActivityTestRule mActivityTestRule; protected GridActivity mActivity; protected BaseGridView mGridView; protected GridLayoutManager mLayoutManager; private GridLayoutManager.OnLayoutCompleteListener mWaitLayoutListener; protected int mOrientation; protected int mNumRows; protected int[] mRemovedItems; private final Comparator mRowSortComparator = new Comparator() { @Override public int compare(View lhs, View rhs) { if (mOrientation == BaseGridView.HORIZONTAL) { return lhs.getLeft() - rhs.getLeft(); } else { return lhs.getTop() - rhs.getTop(); } }; }; /** * Verify margins between items on same row are same. */ private final Runnable mVerifyLayout = new Runnable() { @Override public void run() { verifyMargin(); } }; @Rule public TestName testName = new TestName(); public static void sendKey(int keyCode) { InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode); } public static void sendRepeatedKeys(int repeats, int keyCode) { for (int i = 0; i < repeats; i++) { InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode); } } private void humanDelay(int delay) throws InterruptedException { if (HUMAN_DELAY) Thread.sleep(delay); } /** * Change size of the Adapter and notifyDataSetChanged. */ private void changeArraySize(final int size) throws Throwable { performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.changeArraySize(size); } }); } static String dumpGridView(BaseGridView gridView) { return "findFocus:" + gridView.getRootView().findFocus() + " isLayoutRequested:" + gridView.isLayoutRequested() + " selectedPosition:" + gridView.getSelectedPosition() + " adapter.itemCount:" + gridView.getAdapter().getItemCount() + " itemAnimator.isRunning:" + gridView.getItemAnimator().isRunning() + " scrollState:" + gridView.getScrollState(); } /** * Change selected position. */ private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable { startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPosition(position, scrollExtra); } }); waitForLayout(false); } private void setSelectedPosition(final int position) throws Throwable { setSelectedPosition(position, 0); } private void setSelectedPositionSmooth(final int position) throws Throwable { mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(position); } }); } /** * Scrolls using given key. */ protected void scroll(int key, Runnable verify) throws Throwable { do { if (verify != null) { mActivityTestRule.runOnUiThread(verify); } sendRepeatedKeys(10, key); try { Thread.sleep(300); } catch (InterruptedException ex) { break; } } while (mGridView.getLayoutManager().isSmoothScrolling() || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE); } protected void scrollToBegin(Runnable verify) throws Throwable { int key; // first move to first column/row if (mOrientation == BaseGridView.HORIZONTAL) { key = KeyEvent.KEYCODE_DPAD_UP; } else { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { key = KeyEvent.KEYCODE_DPAD_RIGHT; } else { key = KeyEvent.KEYCODE_DPAD_LEFT; } } scroll(key, null); if (mOrientation == BaseGridView.HORIZONTAL) { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { key = KeyEvent.KEYCODE_DPAD_RIGHT; } else { key = KeyEvent.KEYCODE_DPAD_LEFT; } } else { key = KeyEvent.KEYCODE_DPAD_UP; } scroll(key, verify); } protected void scrollToEnd(Runnable verify) throws Throwable { int key; // first move to first column/row if (mOrientation == BaseGridView.HORIZONTAL) { key = KeyEvent.KEYCODE_DPAD_UP; } else { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { key = KeyEvent.KEYCODE_DPAD_RIGHT; } else { key = KeyEvent.KEYCODE_DPAD_LEFT; } } scroll(key, null); if (mOrientation == BaseGridView.HORIZONTAL) { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { key = KeyEvent.KEYCODE_DPAD_LEFT; } else { key = KeyEvent.KEYCODE_DPAD_RIGHT; } } else { key = KeyEvent.KEYCODE_DPAD_DOWN; } scroll(key, verify); } /** * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL). */ protected View[][] sortByRows() { final HashMap> rows = new HashMap>(); ArrayList rowLocations = new ArrayList<>(); for (int i = 0; i < mGridView.getChildCount(); i++) { View v = mGridView.getChildAt(i); int rowLocation; if (mOrientation == BaseGridView.HORIZONTAL) { rowLocation = v.getTop(); } else { rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL ? v.getRight() : v.getLeft(); } ArrayList views = rows.get(rowLocation); if (views == null) { views = new ArrayList(); rows.put(rowLocation, views); rowLocations.add(rowLocation); } views.add(v); } Object[] sortedLocations = rowLocations.toArray(); Arrays.sort(sortedLocations); if (mNumRows != rows.size()) { assertEquals("Dump Views by rows "+rows, mNumRows, rows.size()); } View[][] sorted = new View[rows.size()][]; for (int i = 0; i < rowLocations.size(); i++) { Integer rowLocation = rowLocations.get(i); ArrayList arr = rows.get(rowLocation); View[] views = arr.toArray(new View[arr.size()]); Arrays.sort(views, mRowSortComparator); sorted[i] = views; } return sorted; } protected void verifyMargin() { View[][] sorted = sortByRows(); for (int row = 0; row < sorted.length; row++) { View[] views = sorted[row]; int margin = -1; for (int i = 1; i < views.length; i++) { if (mOrientation == BaseGridView.HORIZONTAL) { assertEquals(mGridView.getHorizontalMargin(), views[i].getLeft() - views[i - 1].getRight()); } else { assertEquals(mGridView.getVerticalMargin(), views[i].getTop() - views[i - 1].getBottom()); } } } } protected void verifyBeginAligned() { View[][] sorted = sortByRows(); int alignedLocation = 0; if (mOrientation == BaseGridView.HORIZONTAL) { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { for (int i = 0; i < sorted.length; i++) { if (i == 0) { alignedLocation = sorted[i][sorted[i].length - 1].getRight(); } else { assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight()); } } } else { for (int i = 0; i < sorted.length; i++) { if (i == 0) { alignedLocation = sorted[i][0].getLeft(); } else { assertEquals(alignedLocation, sorted[i][0].getLeft()); } } } } else { for (int i = 0; i < sorted.length; i++) { if (i == 0) { alignedLocation = sorted[i][0].getTop(); } else { assertEquals(alignedLocation, sorted[i][0].getTop()); } } } } protected int[] getEndEdges() { View[][] sorted = sortByRows(); int[] edges = new int[sorted.length]; if (mOrientation == BaseGridView.HORIZONTAL) { if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { for (int i = 0; i < sorted.length; i++) { edges[i] = sorted[i][0].getLeft(); } } else { for (int i = 0; i < sorted.length; i++) { edges[i] = sorted[i][sorted[i].length - 1].getRight(); } } } else { for (int i = 0; i < sorted.length; i++) { edges[i] = sorted[i][sorted[i].length - 1].getBottom(); } } return edges; } protected void verifyEdgesSame(int[] edges, int[] edges2) { assertEquals(edges.length, edges2.length); for (int i = 0; i < edges.length; i++) { assertEquals(edges[i], edges2[i]); } } protected void verifyBoundCount(int count) { if (mActivity.getBoundCount() != count) { StringBuffer b = new StringBuffer(); b.append("ItemsLength: "); for (int i = 0; i < mActivity.mItemLengths.length; i++) { b.append(mActivity.mItemLengths[i]).append(","); } assertEquals("Bound count does not match, ItemsLengths: "+ b, count, mActivity.getBoundCount()); } } private static int getCenterY(View v) { return (v.getTop() + v.getBottom())/2; } private static int getCenterX(View v) { return (v.getLeft() + v.getRight())/2; } private void initActivity(Intent intent) throws Throwable { mActivityTestRule = new ActivityTestRule(GridActivity.class, false, false); mActivity = mActivityTestRule.launchActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.setTitle(testName.getMethodName()); } }); Thread.sleep(1000); mGridView = mActivity.mGridView; mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager(); } @After public void clearTest() { mWaitLayoutListener = null; mLayoutManager = null; mGridView = null; mActivity = null; mActivityTestRule = null; } /** * Must be called before waitForLayout() to prepare layout listener. */ protected void startWaitLayout() { if (mWaitLayoutListener != null) { throw new IllegalStateException("startWaitLayout() already called"); } if (mLayoutManager.mLayoutCompleteListener != null) { throw new IllegalStateException("Cannot startWaitLayout()"); } mWaitLayoutListener = mLayoutManager.mLayoutCompleteListener = mock(GridLayoutManager.OnLayoutCompleteListener.class); } /** * wait layout to be called and remove the listener. */ protected void waitForLayout() { waitForLayout(true); } /** * wait layout to be called and remove the listener. * @param force True if always wait regardless if layout requested */ protected void waitForLayout(boolean force) { if (mWaitLayoutListener == null) { throw new IllegalStateException("startWaitLayout() not called"); } if (mWaitLayoutListener != mLayoutManager.mLayoutCompleteListener) { throw new IllegalStateException("layout listener inconistent"); } try { if (force || mGridView.isLayoutRequested()) { verify(mWaitLayoutListener, timeout(WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS).atLeastOnce()) .onLayoutCompleted(any(RecyclerView.State.class)); } } finally { mWaitLayoutListener = null; mLayoutManager.mLayoutCompleteListener = null; } } /** * If currently running animator, wait for it to finish, otherwise return immediately. * To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has * processed adapter change. */ protected void waitForItemAnimation(int timeoutMs) throws Throwable { final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock( RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().isRunning(listener); } }); verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished(); } protected void waitForItemAnimation() throws Throwable { waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS); } /** * wait animation start */ protected void waitForItemAnimationStart() throws Throwable { long totalWait = 0; while (!mGridView.getItemAnimator().isRunning()) { Thread.sleep(10); if ((totalWait += 10) > WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS) { throw new RuntimeException("waitForItemAnimationStart Timeout"); } } } /** * Run task in UI thread and wait for layout and ItemAnimator finishes. */ protected void performAndWaitForAnimation(Runnable task) throws Throwable { startWaitLayout(); mActivityTestRule.runOnUiThread(task); waitForLayout(); waitForItemAnimation(); } protected void waitForScrollIdle() throws Throwable { waitForScrollIdle(null); } /** * Wait for grid view stop scroll and optionally verify state of grid view. */ protected void waitForScrollIdle(Runnable verify) throws Throwable { Thread.sleep(100); int total = 0; while (mGridView.getLayoutManager().isSmoothScrolling() || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) { if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) { throw new RuntimeException("waitForScrollIdle Timeout"); } try { Thread.sleep(100); } catch (InterruptedException ex) { break; } if (verify != null) { mActivityTestRule.runOnUiThread(verify); } } } @Test public void testThreeRowHorizontalBasic() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; scrollToEnd(mVerifyLayout); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } static class DividerDecoration extends RecyclerView.ItemDecoration { private ColorDrawable mTopDivider; private ColorDrawable mBottomDivider; private int mLeftOffset; private int mRightOffset; private int mTopOffset; private int mBottomOffset; DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) { mLeftOffset = leftOffset; mTopOffset = topOffset; mRightOffset = rightOffset; mBottomOffset = bottomOffset; } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { if (mTopDivider == null) { mTopDivider = new ColorDrawable(Color.RED); } if (mBottomDivider == null) { mBottomDivider = new ColorDrawable(Color.BLUE); } final int childCount = parent.getChildCount(); final int width = parent.getWidth(); for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { final View view = parent.getChildAt(childViewIndex); mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY()); mTopDivider.draw(c); mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width, (int) view.getY() + view.getHeight() + mBottomOffset); mBottomDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.left = mLeftOffset; outRect.top = mTopOffset; outRect.right = mRightOffset; outRect.bottom = mBottomOffset; } } @Test public void testItemDecorationAndMargins() throws Throwable { final int leftMargin = 3; final int topMargin = 4; final int rightMargin = 7; final int bottomMargin = 8; final int itemHeight = 100; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight}); intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS, new int[]{leftMargin, topMargin, rightMargin, bottomMargin}); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; final int paddingLeft = mGridView.getPaddingLeft(); final int paddingTop = mGridView.getPaddingTop(); final int verticalSpace = mGridView.getVerticalMargin(); final int decorationLeft = 17; final int decorationTop = 1; final int decorationRight = 19; final int decorationBottom = 2; performAndWaitForAnimation(new Runnable() { @Override public void run() { mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop, decorationRight, decorationBottom)); } }); View child0 = mGridView.getChildAt(0); View child1 = mGridView.getChildAt(1); View child2 = mGridView.getChildAt(2); assertEquals(itemHeight, child0.getBottom() - child0.getTop()); // verify left margins assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft()); assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft()); assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft()); // verify top bottom margins and decoration offset assertEquals(paddingTop + topMargin + decorationTop, child0.getTop()); assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin, child1.getTop() - child0.getBottom()); assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin, child2.getTop() - child1.getBottom()); } @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) @Test public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable { final int leftMargin = 3; final int topMargin = 4; final int rightMargin = 7; final int bottomMargin = 8; final int itemHeight = 100; final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight}); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout); intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS, new int[]{leftMargin, topMargin, rightMargin, bottomMargin}); intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; final int paddingLeft = mGridView.getPaddingLeft(); final int paddingTop = mGridView.getPaddingTop(); final int verticalSpace = mGridView.getVerticalMargin(); final int decorationLeft = 17; final int decorationTop = 1; final int decorationRight = 19; final int decorationBottom = 2; final Rect opticalPaddings = new Rect(); mGridView.getResources().getDrawable(ninePatchDrawableResourceId) .getPadding(opticalPaddings); final int opticalInsetsLeft = opticalPaddings.left; final int opticalInsetsTop = opticalPaddings.top; final int opticalInsetsRight = opticalPaddings.right; final int opticalInsetsBottom = opticalPaddings.bottom; assertTrue(opticalInsetsLeft > 0); assertTrue(opticalInsetsTop > 0); assertTrue(opticalInsetsRight > 0); assertTrue(opticalInsetsBottom > 0); performAndWaitForAnimation(new Runnable() { @Override public void run() { mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop, decorationRight, decorationBottom)); } }); View child0 = mGridView.getChildAt(0); View child1 = mGridView.getChildAt(1); View child2 = mGridView.getChildAt(2); assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom, child0.getBottom() - child0.getTop()); // verify left margins decoration and optical insets assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft, child0.getLeft()); assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft, child1.getLeft()); assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft, child2.getLeft()); // verify top bottom margins decoration offset and optical insets assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop); assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin, (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom)); assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin, (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom)); } @Test public void testThreeColumnVerticalBasic() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; scrollToEnd(mVerifyLayout); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } @Test public void testRedundantAppendRemove() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_testredundantappendremove); intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{ 149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207, 157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159, 139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184, 155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131, 226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234, 173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216, 239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128, 152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183, 221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171, 180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129}); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; scrollToEnd(mVerifyLayout); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } @Test public void testRedundantAppendRemove2() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid_testredundantappendremove2); intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{ 318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252, 237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213, 216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355, 225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315, 299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351}); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager(); // test append without staggered result cache scrollToEnd(mVerifyLayout); int[] endEdges = getEndEdges(); scrollToBegin(mVerifyLayout); verifyBeginAligned(); // now test append with staggered result cache changeArraySize(3); assertEquals("Staggerd cache should be kept as is when no item size change", 100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size()); changeArraySize(100); scrollToEnd(mVerifyLayout); // we should get same aligned end edges int[] endEdges2 = getEndEdges(); verifyEdgesSame(endEdges, endEdges2); } @Test public void testLayoutWhenAViewIsInvalidated() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000); intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mNumRows = 1; initActivity(intent); mOrientation = BaseGridView.VERTICAL; waitOneUiCycle(); // push views to cache. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.mItemLengths[0] = mActivity.mItemLengths[0] * 3; mActivity.mGridView.getAdapter().notifyItemChanged(0); } }); waitForItemAnimation(); // notifyDataSetChange will mark the cached views FLAG_INVALID mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.mGridView.getAdapter().notifyDataSetChanged(); } }); waitForItemAnimation(); // Cached views will be added in prelayout with FLAG_INVALID, in post layout we should // handle it properly mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.mItemLengths[0] = mActivity.mItemLengths[0] / 3; mActivity.mGridView.getAdapter().notifyItemChanged(0); } }); waitForItemAnimation(); } @Test public void testWrongInsertViewIndexInFastRelayout() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mNumRows = 1; initActivity(intent); mOrientation = BaseGridView.VERTICAL; // removing two children, they will be hidden views as first 2 children of RV. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setRemoveDuration(2000); mActivity.removeItems(0, 2); } }); waitForItemAnimationStart(); // add three views and notify change of the first item. startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.addItems(0, new int[]{161, 161, 161}); } }); waitForLayout(); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getAdapter().notifyItemChanged(0); } }); waitForLayout(); // after layout, the viewholder should still be the first child of LayoutManager. assertEquals(0, mGridView.getChildAdapterPosition( mGridView.getLayoutManager().getChildAt(0))); } @Test public void testMoveIntoPrelayoutItems() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mNumRows = 1; initActivity(intent); mOrientation = BaseGridView.VERTICAL; final int lastItemPos = mGridView.getChildCount() - 1; assertTrue(mGridView.getChildCount() >= 4); // notify change of 3 items, so prelayout will layout extra 3 items, then move an item // into the extra layout range. Post layout's fastRelayout() should handle this properly. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getAdapter().notifyItemChanged(lastItemPos - 3); mGridView.getAdapter().notifyItemChanged(lastItemPos - 2); mGridView.getAdapter().notifyItemChanged(lastItemPos - 1); mActivity.moveItem(900, lastItemPos + 2, true); } }); waitForItemAnimation(); } @Test public void testMoveIntoPrelayoutItems2() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mNumRows = 1; initActivity(intent); mOrientation = BaseGridView.VERTICAL; setSelectedPosition(999); final int firstItemPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0)); assertTrue(mGridView.getChildCount() >= 4); // notify change of 3 items, so prelayout will layout extra 3 items, then move an item // into the extra layout range. Post layout's fastRelayout() should handle this properly. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getAdapter().notifyItemChanged(firstItemPos + 1); mGridView.getAdapter().notifyItemChanged(firstItemPos + 2); mGridView.getAdapter().notifyItemChanged(firstItemPos + 3); mActivity.moveItem(0, firstItemPos - 2, true); } }); waitForItemAnimation(); } void preparePredictiveLayout() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setAddDuration(1000); mGridView.getItemAnimator().setRemoveDuration(1000); mGridView.getItemAnimator().setMoveDuration(1000); mGridView.getItemAnimator().setChangeDuration(1000); mGridView.setSelectedPositionSmooth(50); } }); waitForScrollIdle(mVerifyLayout); } @Test public void testPredictiveLayoutAdd1() throws Throwable { preparePredictiveLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.addItems(51, new int[]{300, 300, 300, 300}); } }); waitForItemAnimationStart(); waitForItemAnimation(); assertEquals(50, mGridView.getSelectedPosition()); assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState()); } @Test public void testPredictiveLayoutAdd2() throws Throwable { preparePredictiveLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.addItems(50, new int[]{300, 300, 300, 300}); } }); waitForItemAnimationStart(); waitForItemAnimation(); assertEquals(54, mGridView.getSelectedPosition()); assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState()); } @Test public void testPredictiveLayoutRemove1() throws Throwable { preparePredictiveLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.removeItems(51, 3); } }); waitForItemAnimationStart(); waitForItemAnimation(); assertEquals(50, mGridView.getSelectedPosition()); assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState()); } @Test public void testPredictiveLayoutRemove2() throws Throwable { preparePredictiveLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.removeItems(47, 3); } }); waitForItemAnimationStart(); waitForItemAnimation(); assertEquals(47, mGridView.getSelectedPosition()); assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState()); } @Test public void testPredictiveLayoutRemove3() throws Throwable { preparePredictiveLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.removeItems(0, 51); } }); waitForItemAnimationStart(); waitForItemAnimation(); assertEquals(0, mGridView.getSelectedPosition()); assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState()); } @Test public void testPredictiveLayoutRemove4() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(50); } }); waitForScrollIdle(); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(0, 49); } }); assertEquals(1, mGridView.getSelectedPosition()); } @Test public void testPredictiveLayoutRemove5() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(50); } }); waitForScrollIdle(); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(50, 40); } }); assertEquals(50, mGridView.getSelectedPosition()); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } void waitOneUiCycle() throws Throwable { mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { } }); } @Test public void testDontPruneMovingItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setMoveDuration(2000); mGridView.setSelectedPosition(50); } }); waitForScrollIdle(); final ArrayList moveViewHolders = new ArrayList(); for (int i = 51;; i++) { RecyclerView.ViewHolder vh = mGridView.findViewHolderForAdapterPosition(i); if (vh == null) { break; } moveViewHolders.add(vh); } mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { // add a lot of items, so we will push everything to right of 51 out side window int[] lots_items = new int[1000]; for (int i = 0; i < lots_items.length; i++) { lots_items[i] = 300; } mActivity.addItems(51, lots_items); } }); waitOneUiCycle(); // run a scroll pass, the scroll pass should not remove the animating views even they are // outside visible areas. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollBy(-3, 0); } }); waitOneUiCycle(); for (int i = 0; i < moveViewHolders.size(); i++) { assertSame(mGridView, moveViewHolders.get(i).itemView.getParent()); } } @Test public void testMoveItemToTheRight() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setAddDuration(2000); mGridView.getItemAnimator().setMoveDuration(2000); mGridView.setSelectedPosition(50); } }); waitForScrollIdle(); RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(51); int lastPos = mGridView.getChildAdapterPosition(mGridView.getChildAt( mGridView.getChildCount() - 1)); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.moveItem(51, 1000, true); } }); final ArrayList moveInViewHolders = new ArrayList(); waitForItemAnimationStart(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) { View v = mGridView.getLayoutManager().getChildAt(i); if (mGridView.getChildAdapterPosition(v) >= 51) { moveInViewHolders.add(v); } } } }); waitOneUiCycle(); assertTrue("prelayout should layout extra items to slide in", moveInViewHolders.size() > lastPos - 51); // run a scroll pass, the scroll pass should not remove the animating views even they are // outside visible areas. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollBy(-3, 0); } }); waitOneUiCycle(); for (int i = 0; i < moveInViewHolders.size(); i++) { assertSame(mGridView, moveInViewHolders.get(i).getParent()); } assertSame(mGridView, moveViewHolder.itemView.getParent()); assertFalse(moveViewHolder.isRecyclable()); waitForItemAnimation(); assertNull(moveViewHolder.itemView.getParent()); assertTrue(moveViewHolder.isRecyclable()); } @Test public void testMoveItemToTheLeft() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setAddDuration(2000); mGridView.getItemAnimator().setMoveDuration(2000); mGridView.setSelectedPosition(1500); } }); waitForScrollIdle(); RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(1499); int firstPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0)); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.moveItem(1499, 1, true); } }); final ArrayList moveInViewHolders = new ArrayList(); waitForItemAnimationStart(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) { View v = mGridView.getLayoutManager().getChildAt(i); if (mGridView.getChildAdapterPosition(v) <= 1499) { moveInViewHolders.add(v); } } } }); waitOneUiCycle(); assertTrue("prelayout should layout extra items to slide in ", moveInViewHolders.size() > 1499 - firstPos); // run a scroll pass, the scroll pass should not remove the animating views even they are // outside visible areas. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollBy(3, 0); } }); waitOneUiCycle(); for (int i = 0; i < moveInViewHolders.size(); i++) { assertSame(mGridView, moveInViewHolders.get(i).getParent()); } assertSame(mGridView, moveViewHolder.itemView.getParent()); assertFalse(moveViewHolder.isRecyclable()); waitForItemAnimation(); assertNull(moveViewHolder.itemView.getParent()); assertTrue(moveViewHolder.isRecyclable()); } @Test public void testContinuousSwapForward() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(150); } }); waitForScrollIdle(mVerifyLayout); for (int i = 150; i < 199; i++) { final int swapIndex = i; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.swap(swapIndex, swapIndex + 1); } }); Thread.sleep(10); } waitForItemAnimation(); assertEquals(199, mGridView.getSelectedPosition()); // check if ItemAnimation finishes at aligned positions: int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft()); } @Test public void testContinuousSwapBackward() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(50); } }); waitForScrollIdle(mVerifyLayout); for (int i = 50; i > 0; i--) { final int swapIndex = i; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.swap(swapIndex, swapIndex - 1); } }); Thread.sleep(10); } waitForItemAnimation(); assertEquals(0, mGridView.getSelectedPosition()); // check if ItemAnimation finishes at aligned positions: int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft()); } @Test public void testSwapAfterScroll() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setMoveDuration(1000); mGridView.setSelectedPositionSmooth(150); } }); waitForScrollIdle(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(151); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { // we want to swap and select new target which is at 150 before swap mGridView.setSelectedPositionSmooth(150); mActivity.swap(150, 151); } }); waitForItemAnimation(); waitForScrollIdle(); assertEquals(151, mGridView.getSelectedPosition()); // check if ItemAnimation finishes at aligned positions: int leftEdge = mGridView.getLayoutManager().findViewByPosition(151).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(151).getLeft()); } @Test public void testItemMovedHorizontal() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(150); } }); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.swap(150, 152); } }); mActivityTestRule.runOnUiThread(mVerifyLayout); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } @Test public void testScrollSecondaryCannotScroll() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; final int topPadding = 2; final int bottomPadding = 2; final int height = mGridView.getHeight(); final int spacing = 2; final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing; final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView; startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { horizontalGridView.setPadding(0, topPadding, 0, bottomPadding); horizontalGridView.setItemSpacing(spacing); horizontalGridView.setNumRows(mNumRows); horizontalGridView.setRowHeight(rowHeight); } }); waitForLayout(); // navigate vertically in first column, first row should always be aligned to top padding for (int i = 0; i < 3; i++) { setSelectedPosition(i); assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView .getTop()); } // navigate vertically in 100th column, first row should always be aligned to top padding for (int i = 300; i < 301; i++) { setSelectedPosition(i); assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(300).itemView .getTop()); } } @Test public void testScrollSecondaryNeedScroll() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; // test a lot of rows so we have to scroll vertically to reach mNumRows = 9; final int topPadding = 2; final int bottomPadding = 2; final int height = mGridView.getHeight(); final int spacing = 2; final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing; final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView; startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { horizontalGridView.setPadding(0, topPadding, 0, bottomPadding); horizontalGridView.setItemSpacing(spacing); horizontalGridView.setNumRows(mNumRows); horizontalGridView.setRowHeight(rowHeight); } }); waitForLayout(); View view; // first row should be aligned to top padding setSelectedPosition(0); assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop()); // middle row should be aligned to keyline (1/2 of screen height) setSelectedPosition(4); view = mGridView.findViewHolderForAdapterPosition(4).itemView; assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2); // last row should be aligned to bottom padding. setSelectedPosition(8); view = mGridView.findViewHolderForAdapterPosition(8).itemView; assertEquals(height, view.getTop() + rowHeight + bottomPadding); setSelectedPositionSmooth(4); waitForScrollIdle(); // middle row should be aligned to keyline (1/2 of screen height) setSelectedPosition(4); view = mGridView.findViewHolderForAdapterPosition(4).itemView; assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2); // first row should be aligned to top padding setSelectedPositionSmooth(0); waitForScrollIdle(); assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop()); } @Test public void testItemMovedVertical() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; mGridView.setSelectedPositionSmooth(150); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.swap(150, 152); } }); mActivityTestRule.runOnUiThread(mVerifyLayout); scrollToEnd(mVerifyLayout); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } @Test public void testAddLastItemHorizontal() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread( new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(49); } } ); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(50, new int[]{150}); } }); // assert new added item aligned to right edge assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(), mGridView.getLayoutManager().findViewByPosition(50).getRight()); } @Test public void testAddMultipleLastItemsHorizontal() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; mActivityTestRule.runOnUiThread( new Runnable() { @Override public void run() { mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE); mGridView.setWindowAlignmentOffsetPercent(50); mGridView.setSelectedPositionSmooth(49); } } ); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(50, new int[]{150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150}); } }); // The focused item will be at center of window View view = mGridView.getLayoutManager().findViewByPosition(49); assertEquals(mGridView.getWidth() / 2, (view.getLeft() + view.getRight()) / 2); } @Test public void testItemAddRemoveHorizontal() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; scrollToEnd(mVerifyLayout); int[] endEdges = getEndEdges(); mGridView.setSelectedPositionSmooth(150); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mRemovedItems = mActivity.removeItems(151, 4); } }); scrollToEnd(mVerifyLayout); mGridView.setSelectedPositionSmooth(150); waitForScrollIdle(mVerifyLayout); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(151, mRemovedItems); } }); scrollToEnd(mVerifyLayout); // we should get same aligned end edges int[] endEdges2 = getEndEdges(); verifyEdgesSame(endEdges, endEdges2); scrollToBegin(mVerifyLayout); verifyBeginAligned(); } @Test public void testSetSelectedPositionDetached() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final int focusToIndex = 49; final ViewGroup parent = (ViewGroup) mGridView.getParent(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { parent.removeView(mGridView); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { parent.addView(mGridView); mGridView.requestFocus(); } }); waitForScrollIdle(); assertEquals(mGridView.getSelectedPosition(), focusToIndex); assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus()); final int focusToIndex2 = 0; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { parent.removeView(mGridView); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPosition(focusToIndex2); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { parent.addView(mGridView); mGridView.requestFocus(); } }); assertEquals(mGridView.getSelectedPosition(), focusToIndex2); waitForScrollIdle(); assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus()); } @Test public void testBug22209986() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final int focusToIndex = mGridView.getChildCount() - 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); waitForScrollIdle(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex + 1); } }); // let the scroll running for a while and requestLayout during scroll Thread.sleep(80); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { assertEquals(mGridView.getScrollState(), BaseGridView.SCROLL_STATE_SETTLING); mGridView.requestLayout(); } }); waitForScrollIdle(); int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft()); } @Test public void testScrollAndRemove() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final int focusToIndex = mGridView.getChildCount() - 1; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(focusToIndex, 1); } }); waitOneUiCycle(); waitForScrollIdle(); int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft()); } @Test public void testScrollAndInsert() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); int[] items = new int[1000]; for (int i = 0; i < items.length; i++) { items[i] = 300 + (int)(Math.random() * 100); } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(150); } }); waitForScrollIdle(mVerifyLayout); View view = mGridView.getChildAt(mGridView.getChildCount() - 1); final int focusToIndex = mGridView.getChildAdapterPosition(view); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { int[] newItems = new int[]{300, 300, 300}; mActivity.addItems(0, newItems); } }); waitForScrollIdle(); int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(topEdge, mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop()); } @Test public void testScrollAndInsertBeforeVisibleItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); int[] items = new int[1000]; for (int i = 0; i < items.length; i++) { items[i] = 300 + (int)(Math.random() * 100); } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(150); } }); waitForScrollIdle(mVerifyLayout); View view = mGridView.getChildAt(mGridView.getChildCount() - 1); final int focusToIndex = mGridView.getChildAdapterPosition(view); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); performAndWaitForAnimation(new Runnable() { @Override public void run() { int[] newItems = new int[]{300, 300, 300}; mActivity.addItems(focusToIndex, newItems); } }); } @Test public void testSmoothScrollAndRemove() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final int focusToIndex = 200; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.removeItems(focusToIndex, 1); } }); assertTrue("removing the index of not attached child should not affect smooth scroller", mGridView.getLayoutManager().isSmoothScrolling()); waitForScrollIdle(); int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft()); } @Test public void testSmoothScrollAndRemove2() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final int focusToIndex = 200; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusToIndex); } }); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { final int removeIndex = mGridView.getChildViewHolder( mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition(); mActivity.removeItems(removeIndex, 1); } }); waitForLayout(); assertTrue("removing the index of attached child should not kill smooth scroller", mGridView.getLayoutManager().isSmoothScrolling()); waitForItemAnimation(); waitForScrollIdle(); int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft()); } @Test public void testPendingSmoothScrollAndRemove() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 630 + (int)(Math.random() * 100); } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); assertTrue(mGridView.getChildAt(0).hasFocus()); // Pressing lots of key to make sure smooth scroller is running mGridView.mLayoutManager.mMaxPendingMoves = 100; for (int i = 0; i < 100; i++) { sendKey(KeyEvent.KEYCODE_DPAD_DOWN); } assertTrue(mGridView.getLayoutManager().isSmoothScrolling()); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { final int removeIndex = mGridView.getChildViewHolder( mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition(); mActivity.removeItems(removeIndex, 1); } }); waitForLayout(); assertTrue("removing the index of attached child should not kill smooth scroller", mGridView.getLayoutManager().isSmoothScrolling()); waitForItemAnimation(); waitForScrollIdle(); int focusIndex = mGridView.getSelectedPosition(); int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); } }); waitForScrollIdle(); assertEquals(topEdge, mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop()); } @Test public void testFocusToFirstItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; performAndWaitForAnimation(new Runnable() { @Override public void run() { mRemovedItems = mActivity.removeItems(0, 200); } }); humanDelay(500); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(0, mRemovedItems); } }); humanDelay(500); assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus()); changeArraySize(0); changeArraySize(200); assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus()); } @Test public void testNonFocusableHorizontal() throws Throwable { final int numItems = 200; final int startPos = 45; final int skips = 20; final int numColumns = 3; final int endPos = startPos + numColumns * (skips + 1); Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = true; } for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) { focusable[i] = false; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); mGridView.setSelectedPositionSmooth(startPos); waitForScrollIdle(mVerifyLayout); if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { sendKey(KeyEvent.KEYCODE_DPAD_LEFT); } else { sendKey(KeyEvent.KEYCODE_DPAD_RIGHT); } waitForScrollIdle(mVerifyLayout); assertEquals(endPos, mGridView.getSelectedPosition()); if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { sendKey(KeyEvent.KEYCODE_DPAD_RIGHT); } else { sendKey(KeyEvent.KEYCODE_DPAD_LEFT); } waitForScrollIdle(mVerifyLayout); assertEquals(startPos, mGridView.getSelectedPosition()); } @Test public void testNoInitialFocusable() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); final int numItems = 100; intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; boolean[] focusable = new boolean[numItems]; final int firstFocusableIndex = 10; for (int i = 0; i < firstFocusableIndex; i++) { focusable[i] = false; } for (int i = firstFocusableIndex; i < focusable.length; i++) { focusable[i] = true; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); assertTrue(mGridView.isFocused()); if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { sendKey(KeyEvent.KEYCODE_DPAD_LEFT); } else { sendKey(KeyEvent.KEYCODE_DPAD_RIGHT); } waitForScrollIdle(mVerifyLayout); assertEquals(firstFocusableIndex, mGridView.getSelectedPosition()); assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus()); } @Test public void testFocusOutOfEmptyListView() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); final int numItems = 100; intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; initActivity(intent); final View horizontalGridView = new HorizontalGridViewEx(mGridView.getContext()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { horizontalGridView.setFocusable(true); horizontalGridView.setFocusableInTouchMode(true); horizontalGridView.setLayoutParams(new ViewGroup.LayoutParams(100, 100)); ((ViewGroup) mGridView.getParent()).addView(horizontalGridView, 0); horizontalGridView.requestFocus(); } }); assertTrue(horizontalGridView.isFocused()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); assertTrue(mGridView.hasFocus()); } @Test public void testTransferFocusToChildWhenGainFocus() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); final int numItems = 100; intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; boolean[] focusable = new boolean[numItems]; final int firstFocusableIndex = 1; for (int i = 0; i < firstFocusableIndex; i++) { focusable[i] = false; } for (int i = firstFocusableIndex; i < focusable.length; i++) { focusable[i] = true; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); assertEquals(firstFocusableIndex, mGridView.getSelectedPosition()); assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus()); } @Test public void testFocusFromSecondChild() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); final int numItems = 100; intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = false; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); // switching Adapter to cause a full rebind, test if it will focus to second item. performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.mNumItems = numItems; mActivity.mItemFocusables[1] = true; mActivity.rebindToNewAdapter(); } }); assertTrue(mGridView.findViewHolderForAdapterPosition(1).itemView.hasFocus()); } @Test public void removeFocusableItemAndFocusableRecyclerViewGetsFocus() throws Throwable { final int numItems = 100; final int numColumns = 3; final int focusableIndex = 2; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = false; } focusable[focusableIndex] = true; intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(focusableIndex); } }); waitForScrollIdle(mVerifyLayout); assertEquals(focusableIndex, mGridView.getSelectedPosition()); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(focusableIndex, 1); } }); assertTrue(dumpGridView(mGridView), mGridView.isFocused()); } @Test public void removeFocusableItemAndUnFocusableRecyclerViewLosesFocus() throws Throwable { final int numItems = 100; final int numColumns = 3; final int focusableIndex = 2; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = false; } focusable[focusableIndex] = true; intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setFocusableInTouchMode(false); mGridView.setFocusable(false); mGridView.setSelectedPositionSmooth(focusableIndex); } }); waitForScrollIdle(mVerifyLayout); assertEquals(focusableIndex, mGridView.getSelectedPosition()); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(focusableIndex, 1); } }); assertFalse(dumpGridView(mGridView), mGridView.hasFocus()); } @Test public void testNonFocusableVertical() throws Throwable { final int numItems = 200; final int startPos = 44; final int skips = 20; final int numColumns = 3; final int endPos = startPos + numColumns * (skips + 1); Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = true; } for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) { focusable[i] = false; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); mGridView.setSelectedPositionSmooth(startPos); waitForScrollIdle(mVerifyLayout); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); waitForScrollIdle(mVerifyLayout); assertEquals(endPos, mGridView.getSelectedPosition()); sendKey(KeyEvent.KEYCODE_DPAD_UP); waitForScrollIdle(mVerifyLayout); assertEquals(startPos, mGridView.getSelectedPosition()); } @Test public void testLtrFocusOutStartDisabled() throws Throwable { final int numItems = 200; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestFocus(); mGridView.setSelectedPositionSmooth(0); } }); waitForScrollIdle(mVerifyLayout); sendKey(KeyEvent.KEYCODE_DPAD_LEFT); waitForScrollIdle(mVerifyLayout); assertTrue(mGridView.hasFocus()); } @Test public void testRtlFocusOutStartDisabled() throws Throwable { final int numItems = 200; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestFocus(); mGridView.setSelectedPositionSmooth(0); } }); waitForScrollIdle(mVerifyLayout); sendKey(KeyEvent.KEYCODE_DPAD_RIGHT); waitForScrollIdle(mVerifyLayout); assertTrue(mGridView.hasFocus()); } @Test public void testTransferFocusable() throws Throwable { final int numItems = 200; final int numColumns = 3; final int startPos = 1; Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = true; } for (int i = 0; i < startPos; i++) { focusable[i] = false; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); changeArraySize(0); assertTrue(mGridView.isFocused()); changeArraySize(numItems); assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus()); } @Test public void testTransferFocusable2() throws Throwable { final int numItems = 200; final int numColumns = 3; final int startPos = 3; // make sure view at startPos is in visible area. Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.HORIZONTAL; mNumRows = numColumns; boolean[] focusable = new boolean[numItems]; for (int i = 0; i < focusable.length; i++) { focusable[i] = true; } for (int i = 0; i < startPos; i++) { focusable[i] = false; } intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable); initActivity(intent); assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus()); changeArraySize(0); assertTrue(mGridView.isFocused()); changeArraySize(numItems); assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus()); } @Test public void testNonFocusableLoseInFastLayout() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); int[] items = new int[300]; for (int i = 0; i < items.length; i++) { items[i] = 480; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; int pressDown = 15; initActivity(intent); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); for (int i = 0; i < pressDown; i++) { sendKey(KeyEvent.KEYCODE_DPAD_DOWN); } waitForScrollIdle(mVerifyLayout); assertFalse(mGridView.isFocused()); } @Test public void testFocusableViewAvailable() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, new boolean[]{false, false, true, false, false}); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { // RecyclerView does not respect focusable and focusableInTouchMode flag, so // set flags in code. mGridView.setFocusableInTouchMode(false); mGridView.setFocusable(false); } }); assertFalse(mGridView.isFocused()); final boolean[] scrolled = new boolean[]{false}; mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy){ if (dy > 0) { scrolled[0] = true; } } }); performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(0, new int[]{200, 300, 500, 500, 200}); } }); waitForScrollIdle(mVerifyLayout); assertFalse("GridView should not be scrolled", scrolled[0]); assertTrue(mGridView.getLayoutManager().findViewByPosition(2).hasFocus()); } @Test public void testSetSelectionWithDelta() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(3); } }); waitForScrollIdle(mVerifyLayout); int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop(); humanDelay(1000); // scroll to position with delta setSelectedPosition(3, 100); int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop(); assertEquals(top1 - 100, top2); // scroll to same position without delta, it will be reset setSelectedPosition(3, 0); int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop(); assertEquals(top1, top3); // scroll invisible item after last visible item final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getLastVisibleIndex(); setSelectedPosition(lastVisiblePos + 1, 100); int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop(); assertEquals(top1 - 100, top4); // scroll invisible item before first visible item final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getFirstVisibleIndex(); setSelectedPosition(firstVisiblePos - 1, 100); int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop(); assertEquals(top1 - 100, top5); // scroll to invisible item that is far away. setSelectedPosition(50, 100); int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop(); assertEquals(top1 - 100, top6); // scroll to invisible item that is far away. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(100); } }); waitForScrollIdle(mVerifyLayout); int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop(); assertEquals(top1, top7); // scroll to invisible item that is far away. setSelectedPosition(10, 50); int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop(); assertEquals(top1 - 50, top8); } @Test public void testSetSelectionWithDeltaInGrid() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(10); } }); waitForScrollIdle(mVerifyLayout); int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10)); humanDelay(500); // scroll to position with delta setSelectedPosition(20, 100); int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20)); assertEquals(top1 - 100, top2); // scroll to same position without delta, it will be reset setSelectedPosition(20, 0); int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20)); assertEquals(top1, top3); // scroll invisible item after last visible item final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getLastVisibleIndex(); setSelectedPosition(lastVisiblePos + 1, 100); int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1)); verifyMargin(); assertEquals(top1 - 100, top4); // scroll invisible item before first visible item final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getFirstVisibleIndex(); setSelectedPosition(firstVisiblePos - 1, 100); int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1)); assertEquals(top1 - 100, top5); // scroll to invisible item that is far away. setSelectedPosition(100, 100); int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100)); assertEquals(top1 - 100, top6); // scroll to invisible item that is far away. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(200); } }); waitForScrollIdle(mVerifyLayout); Thread.sleep(500); int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200)); assertEquals(top1, top7); // scroll to invisible item that is far away. setSelectedPosition(10, 50); int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10)); assertEquals(top1 - 50, top8); } @Test public void testSetSelectionWithDeltaInGrid1() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{ 193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137, 188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205, 223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222, 150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236, 123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128, 192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218, 192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191, 212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141, 156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191, 132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184, 164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226, 132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226, 127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169, 182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220, 178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125, 214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122, 188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191, 199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210, 164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130, 137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227, 193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154, 192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124, 172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174, 146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225, 174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162, 192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221, 120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190, 204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125, 202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123, 237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162, 139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199, 215,213,122,197 }); intent.putExtra(GridActivity.EXTRA_STAGGERED, true); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(10); } }); waitForScrollIdle(mVerifyLayout); int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10)); humanDelay(500); // scroll to position with delta setSelectedPosition(20, 100); int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20)); assertEquals(top1 - 100, top2); // scroll to same position without delta, it will be reset setSelectedPosition(20, 0); int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20)); assertEquals(top1, top3); // scroll invisible item after last visible item final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getLastVisibleIndex(); setSelectedPosition(lastVisiblePos + 1, 100); int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1)); verifyMargin(); assertEquals(top1 - 100, top4); // scroll invisible item before first visible item final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager()) .mGrid.getFirstVisibleIndex(); setSelectedPosition(firstVisiblePos - 1, 100); int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1)); assertEquals(top1 - 100, top5); // scroll to invisible item that is far away. setSelectedPosition(100, 100); int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100)); assertEquals(top1 - 100, top6); // scroll to invisible item that is far away. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(200); } }); waitForScrollIdle(mVerifyLayout); Thread.sleep(500); int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200)); assertEquals(top1, top7); // scroll to invisible item that is far away. setSelectedPosition(10, 50); int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10)); assertEquals(top1 - 50, top8); } @Test public void testSmoothScrollSelectionEvents() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(30); } }); waitForScrollIdle(mVerifyLayout); humanDelay(500); final ArrayList selectedPositions = new ArrayList(); mGridView.setOnChildSelectedListener(new OnChildSelectedListener() { @Override public void onChildSelected(ViewGroup parent, View view, int position, long id) { selectedPositions.add(position); } }); sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP); humanDelay(500); waitForScrollIdle(mVerifyLayout); // should only get childselected event for item 0 once assertTrue(selectedPositions.size() > 0); assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue()); for (int i = selectedPositions.size() - 2; i >= 0; i--) { assertFalse(0 == selectedPositions.get(i).intValue()); } } @Test public void testSmoothScrollSelectionEventsLinear() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(10); } }); waitForScrollIdle(mVerifyLayout); humanDelay(500); final ArrayList selectedPositions = new ArrayList(); mGridView.setOnChildSelectedListener(new OnChildSelectedListener() { @Override public void onChildSelected(ViewGroup parent, View view, int position, long id) { selectedPositions.add(position); } }); sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP); humanDelay(500); waitForScrollIdle(mVerifyLayout); // should only get childselected event for item 0 once assertTrue(selectedPositions.size() > 0); assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue()); for (int i = selectedPositions.size() - 2; i >= 0; i--) { assertFalse(0 == selectedPositions.get(i).intValue()); } } @Test public void testScrollToNoneExisting() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 3; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(99); } }); waitForScrollIdle(mVerifyLayout); humanDelay(500); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(50); } }); Thread.sleep(100); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestLayout(); mGridView.setSelectedPositionSmooth(0); } }); waitForScrollIdle(mVerifyLayout); humanDelay(500); } @Test public void testSmoothscrollerInterrupted() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 680; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); assertTrue(mGridView.getChildAt(0).hasFocus()); // Pressing lots of key to make sure smooth scroller is running for (int i = 0; i < 20; i++) { sendKey(KeyEvent.KEYCODE_DPAD_DOWN); } while (mGridView.getLayoutManager().isSmoothScrolling() || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) { // Repeatedly pressing to make sure pending keys does not drop to zero. sendKey(KeyEvent.KEYCODE_DPAD_DOWN); } } @Test public void testSmoothscrollerCancelled() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 680; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); assertTrue(mGridView.getChildAt(0).hasFocus()); int targetPosition = items.length - 1; mGridView.setSelectedPositionSmooth(targetPosition); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.stopScroll(); } }); waitForScrollIdle(); waitForItemAnimation(); assertEquals(mGridView.getSelectedPosition(), targetPosition); assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition), mGridView.findFocus()); } @Test public void testSetNumRowsAndAddItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[2]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); mActivity.addItems(items.length, new int[]{300}); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { ((VerticalGridView) mGridView).setNumColumns(2); } }); Thread.sleep(1000); assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft()); } @Test public void testRequestLayoutBugInLayout() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(1); } }); waitForScrollIdle(mVerifyLayout); sendKey(KeyEvent.KEYCODE_DPAD_UP); waitForScrollIdle(mVerifyLayout); assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString()); } @Test public void testChangeLayoutInChild() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_wrap_content); intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true); int[] items = new int[2]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(0); } }); waitForScrollIdle(mVerifyLayout); verifyMargin(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(1); } }); waitForScrollIdle(mVerifyLayout); verifyMargin(); } @Test public void testWrapContent() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid_wrap); int[] items = new int[200]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.attachToNewAdapter(new int[0]); } }); } @Test public void testZeroFixedSecondarySize() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_measured_with_zero); intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true); int[] items = new int[2]; for (int i = 0; i < items.length; i++) { items[i] = 0; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); } @Test public void testChildStates() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 200; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD); final SparseArray container = new SparseArray(); // 1 Save view states mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0)) .getText()), 0, 1); Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1)) .getText()), 0, 1); mGridView.saveHierarchyState(container); } }); // 2 Change view states mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0)) .getText()), 1, 2); Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1)) .getText()), 1, 2); } }); // 3 Detached and re-attached, should still maintain state of (2) mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(1); } }); waitForScrollIdle(mVerifyLayout); assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1); assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2); assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1); assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2); // 4 Recycled and rebound, should load state from (2) mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(20); } }); waitForScrollIdle(mVerifyLayout); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setSelectedPositionSmooth(0); } }); waitForScrollIdle(mVerifyLayout); assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1); assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2); assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1); assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2); } @Test public void testNoDispatchSaveChildState() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 200; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD); final SparseArray container = new SparseArray(); // 1. Set text selection, save view states should do nothing on child mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < mGridView.getChildCount(); i++) { Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i)) .getText()), 0, 1); } mGridView.saveHierarchyState(container); } }); // 2. clear the text selection mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < mGridView.getChildCount(); i++) { Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i)) .getText())); } } }); // 3. Restore view states should be a no-op for child mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.restoreHierarchyState(container); for (int i = 0; i < mGridView.getChildCount(); i++) { assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart()); assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd()); } } }); } static interface ViewTypeProvider { public int getViewType(int position); } static interface ItemAlignmentFacetProvider { public ItemAlignmentFacet getItemAlignmentFacet(int viewType); } static class TwoViewTypesProvider implements ViewTypeProvider { static int VIEW_TYPE_FIRST = 1; static int VIEW_TYPE_DEFAULT = 0; @Override public int getViewType(int position) { if (position == 0) { return VIEW_TYPE_FIRST; } else { return VIEW_TYPE_DEFAULT; } } } static class ChangeableViewTypesProvider implements ViewTypeProvider { static SparseIntArray sViewTypes = new SparseIntArray(); @Override public int getViewType(int position) { return sViewTypes.get(position); } public static void clear() { sViewTypes.clear(); } public static void setViewType(int position, int type) { sViewTypes.put(position, type); } } static class PositionItemAlignmentFacetProviderForRelativeLayout1 implements ItemAlignmentFacetProvider { ItemAlignmentFacet mMultipleFacet; PositionItemAlignmentFacetProviderForRelativeLayout1() { mMultipleFacet = new ItemAlignmentFacet(); ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t1); defs[1] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[1].setItemAlignmentViewId(R.id.t2); defs[1].setItemAlignmentOffsetPercent(100); defs[1].setItemAlignmentOffset(-10); mMultipleFacet.setAlignmentDefs(defs); } @Override public ItemAlignmentFacet getItemAlignmentFacet(int position) { if (position == 0) { return mMultipleFacet; } else { return null; } } } @Test public void testMultipleScrollPosition1() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, TwoViewTypesProvider.class.getName()); // Set ItemAlignment for each ViewHolder and view type, ViewHolder should // override the view type settings. intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS, PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS, ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); waitForScrollIdle(mVerifyLayout); final View v = mGridView.getChildAt(0); View t1 = v.findViewById(R.id.t1); int t1align = (t1.getTop() + t1.getBottom()) / 2; View t2 = v.findViewById(R.id.t2); int t2align = t2.getBottom() - 10; assertEquals("Expected alignment for 2nd textview", mGridView.getPaddingTop() - (t2align - t1align), v.getTop()); } static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements ItemAlignmentFacetProvider { ItemAlignmentFacet mMultipleFacet; PositionItemAlignmentFacetProviderForRelativeLayout2() { mMultipleFacet = new ItemAlignmentFacet(); ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t1); defs[0].setItemAlignmentOffsetPercent(0); defs[1] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[1].setItemAlignmentViewId(R.id.t2); defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); defs[1].setItemAlignmentOffset(-10); mMultipleFacet.setAlignmentDefs(defs); } @Override public ItemAlignmentFacet getItemAlignmentFacet(int position) { if (position == 0) { return mMultipleFacet; } else { return null; } } } @Test public void testMultipleScrollPosition2() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, TwoViewTypesProvider.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS, PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); waitForScrollIdle(mVerifyLayout); final View v = mGridView.getChildAt(0); View t1 = v.findViewById(R.id.t1); int t1align = t1.getTop(); View t2 = v.findViewById(R.id.t2); int t2align = t2.getTop() - 10; assertEquals("Expected alignment for 2nd textview", mGridView.getPaddingTop() - (t2align - t1align), v.getTop()); } static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements ItemAlignmentFacetProvider { ItemAlignmentFacet mMultipleFacet; ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() { mMultipleFacet = new ItemAlignmentFacet(); ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t1); defs[0].setItemAlignmentOffsetPercent(0); defs[1] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[1].setItemAlignmentViewId(R.id.t2); defs[1].setItemAlignmentOffsetPercent(100); defs[1].setItemAlignmentOffset(-10); mMultipleFacet.setAlignmentDefs(defs); } @Override public ItemAlignmentFacet getItemAlignmentFacet(int viewType) { if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) { return mMultipleFacet; } else { return null; } } } @Test public void testMultipleScrollPosition3() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, TwoViewTypesProvider.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS, ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); waitForScrollIdle(mVerifyLayout); final View v = mGridView.getChildAt(0); View t1 = v.findViewById(R.id.t1); int t1align = t1.getTop(); View t2 = v.findViewById(R.id.t2); int t2align = t2.getBottom() - 10; assertEquals("Expected alignment for 2nd textview", mGridView.getPaddingTop() - (t2align - t1align), v.getTop()); } @Test public void testSelectionAndAddItemInOneCycle() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(0, new int[]{300, 300}); mGridView.setSelectedPosition(0); } }); assertEquals(0, mGridView.getSelectedPosition()); } @Test public void testSelectViewTaskSmoothWithAdapterChange() throws Throwable { testSelectViewTaskWithAdapterChange(true /*smooth*/); } @Test public void testSelectViewTaskWithAdapterChange() throws Throwable { testSelectViewTaskWithAdapterChange(false /*smooth*/); } private void testSelectViewTaskWithAdapterChange(final boolean smooth) throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final View firstView = mGridView.getLayoutManager().findViewByPosition(0); final View[] selectedViewByTask = new View[1]; final ViewHolderTask task = new ViewHolderTask() { @Override public void run(RecyclerView.ViewHolder viewHolder) { selectedViewByTask[0] = viewHolder.itemView; } }; performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.removeItems(0, 1); if (smooth) { mGridView.setSelectedPositionSmooth(0, task); } else { mGridView.setSelectedPosition(0, task); } } }); assertEquals(0, mGridView.getSelectedPosition()); assertNotNull(selectedViewByTask[0]); assertNotSame(firstView, selectedViewByTask[0]); assertSame(mGridView.getLayoutManager().findViewByPosition(0), selectedViewByTask[0]); } @Test public void testNotifyItemTypeChangedSelectionEvent() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, ChangeableViewTypesProvider.class.getName()); ChangeableViewTypesProvider.clear(); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; final ArrayList selectedLog = new ArrayList(); mGridView.setOnChildSelectedListener(new OnChildSelectedListener() { @Override public void onChildSelected(ViewGroup parent, View view, int position, long id) { selectedLog.add(position); } }); performAndWaitForAnimation(new Runnable() { @Override public void run() { ChangeableViewTypesProvider.setViewType(0, 1); mGridView.getAdapter().notifyItemChanged(0, 1); } }); assertEquals(0, mGridView.getSelectedPosition()); assertEquals(selectedLog.size(), 1); assertEquals((int) selectedLog.get(0), 0); } @Test public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0); initActivity(intent); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; performAndWaitForAnimation(new Runnable() { @Override public void run() { mActivity.addItems(0, new int[]{300, 300}); mGridView.setSelectedPositionSmooth(0); } }); assertEquals(0, mGridView.getSelectedPosition()); } @Test public void testExtraLayoutSpace() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); initActivity(intent); final int windowSize = mGridView.getHeight(); final int extraLayoutSize = windowSize; mOrientation = BaseGridView.VERTICAL; mNumRows = 1; // add extra layout space startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setExtraLayoutSpace(extraLayoutSize); } }); waitForLayout(); View v; v = mGridView.getChildAt(mGridView.getChildCount() - 1); assertTrue(v.getTop() < windowSize + extraLayoutSize); assertTrue(v.getBottom() >= windowSize + extraLayoutSize - mGridView.getVerticalMargin()); mGridView.setSelectedPositionSmooth(150); waitForScrollIdle(mVerifyLayout); v = mGridView.getChildAt(0); assertTrue(v.getBottom() > - extraLayoutSize); assertTrue(v.getTop() <= -extraLayoutSize + mGridView.getVerticalMargin()); // clear extra layout space mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setExtraLayoutSpace(0); verifyMargin(); } }); Thread.sleep(50); v = mGridView.getChildAt(mGridView.getChildCount() - 1); assertTrue(v.getTop() < windowSize); assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin()); } @Test public void testFocusFinder() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; // test focus from button to vertical grid view final View button = mActivity.findViewById(R.id.button); assertTrue(button.isFocused()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); assertFalse(mGridView.isFocused()); assertTrue(mGridView.hasFocus()); // FocusFinder should find last focused(2nd) item on DPAD_DOWN final View secondChild = mGridView.getChildAt(1); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { secondChild.requestFocus(); button.requestFocus(); } }); assertTrue(button.isFocused()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); assertTrue(secondChild.isFocused()); // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused // (2nd) item on DPAD_DOWN. mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { button.requestFocus(); } }); mGridView.setFocusable(false); mGridView.setFocusableInTouchMode(false); assertTrue(button.isFocused()); sendKey(KeyEvent.KEYCODE_DPAD_DOWN); assertTrue(secondChild.isFocused()); } @Test public void testRestoreIndexAndAddItems() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; assertEquals(mGridView.getSelectedPosition(), 0); final SparseArray states = new SparseArray<>(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.saveHierarchyState(states); mGridView.setAdapter(null); } }); performAndWaitForAnimation(new Runnable() { @Override public void run() { mGridView.restoreHierarchyState(states); mActivity.attachToNewAdapter(new int[0]); mActivity.addItems(0, new int[]{100, 100, 100, 100}); } }); assertEquals(mGridView.getSelectedPosition(), 0); } @Test public void test27766012() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; // set remove animator two seconds mGridView.getItemAnimator().setRemoveDuration(2000); final View view = mGridView.getChildAt(1); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { view.requestFocus(); } }); assertTrue(view.hasFocus()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.removeItems(0, 2); } }); // wait one second, removing second view is still attached to parent Thread.sleep(1000); assertSame(view.getParent(), mGridView); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { // refocus to the removed item and do a focus search. view.requestFocus(); view.focusSearch(View.FOCUS_UP); } }); } @Test public void testBug27258366() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; // move item1 500 pixels right, when focus is on item1, default focus finder will pick // item0 and item2 for the best match of focusSearch(FOCUS_LEFT). The grid widget // must override default addFocusables(), not to add item0 or item2. mActivity.mAdapterListener = new GridActivity.AdapterListener() { @Override public void onBind(RecyclerView.ViewHolder vh, int position) { if (position == 1) { vh.itemView.setPaddingRelative(500, 0, 0, 0); } else { vh.itemView.setPaddingRelative(0, 0, 0, 0); } } }; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getAdapter().notifyDataSetChanged(); } }); Thread.sleep(100); final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { secondChild.requestFocus(); } }); sendKey(KeyEvent.KEYCODE_DPAD_LEFT); Thread.sleep(100); final View button = mActivity.findViewById(R.id.button); assertTrue(button.isFocused()); } @Test public void testAccessibility() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); initActivity(intent); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; assertTrue(0 == mGridView.getSelectedPosition()); final RecyclerViewAccessibilityDelegate delegateCompat = mGridView .getCompatAccessibilityDelegate(); final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info); } }); assertTrue("test sanity", info.isScrollable()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null); } }); waitForScrollIdle(mVerifyLayout); int selectedPosition1 = mGridView.getSelectedPosition(); assertTrue(0 < selectedPosition1); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info); } }); assertTrue("test sanity", info.isScrollable()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null); } }); waitForScrollIdle(mVerifyLayout); int selectedPosition2 = mGridView.getSelectedPosition(); assertTrue(selectedPosition2 < selectedPosition1); } void slideInAndWaitIdle() throws Throwable { slideInAndWaitIdle(5000); } void slideInAndWaitIdle(long timeout) throws Throwable { // animateIn() would reset position mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateIn(); } }); PollingCheck.waitFor(timeout, new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return !mGridView.getLayoutManager().isSmoothScrolling() && mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); } @Test public void testAnimateOutBlockScrollTo() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); // wait until sliding out. PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop(); } }); // scrollToPosition() should not affect slideOut status mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollToPosition(0); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view slided Out", mGridView.getChildAt(0).getTop() >= mGridView.getHeight()); slideInAndWaitIdle(); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); } @Test public void testAnimateOutBlockSmoothScrolling() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); int[] items = new int[30]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); // wait until sliding out. PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop(); } }); // smoothScrollToPosition() should not affect slideOut status mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.smoothScrollToPosition(29); } }); PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view slided Out", mGridView.getChildAt(0).getTop() >= mGridView.getHeight()); slideInAndWaitIdle(); View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1); assertSame("Scrolled to last child", mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild); } @Test public void testAnimateOutBlockLongScrollTo() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); int[] items = new int[30]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); // wait until sliding out. PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop(); } }); // smoothScrollToPosition() should not affect slideOut status mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollToPosition(29); } }); PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view slided Out", mGridView.getChildAt(0).getTop() >= mGridView.getHeight()); slideInAndWaitIdle(); View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1); assertSame("Scrolled to last child", mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild); } @Test public void testAnimateOutBlockLayout() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); // wait until sliding out. PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop(); } }); // change adapter should not affect slideOut status mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mActivity.changeItem(0, 200); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view slided Out", mGridView.getChildAt(0).getTop() >= mGridView.getHeight()); assertEquals("onLayout suppressed during slide out", 300, mGridView.getChildAt(0).getHeight()); slideInAndWaitIdle(); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); // size of item should be updated immediately after slide in animation finishes: PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return 200 == mGridView.getChildAt(0).getHeight(); } }); } @Test public void testAnimateOutBlockFocusChange() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); mActivity.findViewById(R.id.button).requestFocus(); } }); assertTrue(mActivity.findViewById(R.id.button).hasFocus()); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop(); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.requestFocus(); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view slided Out", mGridView.getChildAt(0).getTop() >= mGridView.getHeight()); slideInAndWaitIdle(); assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop()); } @Test public void testHorizontalAnimateOutBlockScrollTo() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(), mGridView.getChildAt(0).getLeft()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getLeft() > mGridView.getPaddingLeft(); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.scrollToPosition(0); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view is slided out", mGridView.getChildAt(0).getLeft() > mGridView.getWidth()); slideInAndWaitIdle(); assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(), mGridView.getChildAt(0).getLeft()); } @Test public void testHorizontalAnimateOutRtl() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear_rtl); int[] items = new int[100]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 1; initActivity(intent); assertEquals("First view is aligned with padding right", mGridView.getWidth() - mGridView.getPaddingRight(), mGridView.getChildAt(0).getRight()); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.animateOut(); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getChildAt(0).getRight() < mGridView.getWidth() - mGridView.getPaddingRight(); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.smoothScrollToPosition(0); } }); PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { @Override public boolean canProceed() { return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE; } }); assertTrue("First view is slided out", mGridView.getChildAt(0).getRight() < 0); slideInAndWaitIdle(); assertEquals("First view is aligned with padding right", mGridView.getWidth() - mGridView.getPaddingRight(), mGridView.getChildAt(0).getRight()); } @Test public void testSmoothScrollerOutRange() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear_with_button_onleft); intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true); int[] items = new int[30]; for (int i = 0; i < items.length; i++) { items[i] = 680; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); final View button = mActivity.findViewById(R.id.button); mActivityTestRule.runOnUiThread(new Runnable() { public void run() { button.requestFocus(); } }); mGridView.setSelectedPositionSmooth(0); waitForScrollIdle(mVerifyLayout); mActivityTestRule.runOnUiThread(new Runnable() { public void run() { mGridView.setSelectedPositionSmooth(120); } }); waitForScrollIdle(mVerifyLayout); assertTrue(button.hasFocus()); int key; if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) { key = KeyEvent.KEYCODE_DPAD_LEFT; } else { key = KeyEvent.KEYCODE_DPAD_RIGHT; } sendKey(key); // the GridView should has focus in its children assertTrue(mGridView.hasFocus()); assertFalse(mGridView.isFocused()); assertEquals(29, mGridView.getSelectedPosition()); } @Test public void testRemoveLastItemWithStableId() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true); int[] items = new int[1]; for (int i = 0; i < items.length; i++) { items[i] = 680; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setRemoveDuration(2000); mActivity.removeItems(0, 1, false); mGridView.getAdapter().notifyDataSetChanged(); } }); Thread.sleep(500); assertEquals(-1, mGridView.getSelectedPosition()); } static void assertNoCollectionItemInfo(AccessibilityNodeInfoCompat info) { AccessibilityNodeInfoCompat.CollectionItemInfoCompat nodeInfoCompat = info.getCollectionItemInfo(); if (nodeInfoCompat == null) { return; } assertTrue(nodeInfoCompat.getRowIndex() < 0); assertTrue(nodeInfoCompat.getColumnIndex() < 0); } /** * This test would need talkback on. */ @Test public void testAccessibilityOfItemsBeingPushedOut() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; initActivity(intent); final int lastPos = mGridView.getChildAdapterPosition( mGridView.getChildAt(mGridView.getChildCount() - 1)); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getLayoutManager().setItemPrefetchEnabled(false); } }); final int numItemsToPushOut = mNumRows; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { // set longer enough so that accessibility service will initialize node // within setImportantForAccessibility(). mGridView.getItemAnimator().setRemoveDuration(2000); mGridView.getItemAnimator().setAddDuration(2000); final int[] newItems = new int[numItemsToPushOut]; final int newItemValue = mActivity.mItemLengths[0]; for (int i = 0; i < newItems.length; i++) { newItems[i] = newItemValue; } mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems); } }); waitForItemAnimation(); } /** * This test simulates talkback by calling setImportanceForAccessibility at end of animation */ @Test public void simulatesAccessibilityOfItemsBeingPushedOut() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; initActivity(intent); final HashSet moveAnimationViews = new HashSet(); mActivity.mImportantForAccessibilityListener = new GridActivity.ImportantForAccessibilityListener() { RecyclerView.LayoutManager mLM = mGridView.getLayoutManager(); @Override public void onImportantForAccessibilityChanged(View view, int newValue) { // simulates talkack, having setImportantForAccessibility to call // onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items. if (moveAnimationViews.contains(view)) { AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(); mLM.onInitializeAccessibilityNodeInfoForItem( null, null, view, info); } } }; final int lastPos = mGridView.getChildAdapterPosition( mGridView.getChildAt(mGridView.getChildCount() - 1)); final int numItemsToPushOut = mNumRows; for (int i = 0; i < numItemsToPushOut; i++) { moveAnimationViews.add( mGridView.getChildAt(mGridView.getChildCount() - 1 - i)); } mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setItemAnimator(new DefaultItemAnimator() { @Override public void onMoveFinished(RecyclerView.ViewHolder item) { moveAnimationViews.remove(item.itemView); } }); mGridView.getLayoutManager().setItemPrefetchEnabled(false); } }); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { final int[] newItems = new int[numItemsToPushOut]; final int newItemValue = mActivity.mItemLengths[0] + 1; for (int i = 0; i < newItems.length; i++) { newItems[i] = newItemValue; } mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems); } }); while (moveAnimationViews.size() != 0) { Thread.sleep(100); } } @Test public void testAccessibilityNodeInfoOnRemovedFirstItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; initActivity(intent); final View lastView = mGridView.findViewHolderForAdapterPosition(0).itemView; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setRemoveDuration(20000); mActivity.removeItems(0, 1); } }); waitForItemAnimationStart(); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView); mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null, lastView, info); assertNoCollectionItemInfo(info); } @Test public void testAccessibilityNodeInfoOnRemovedLastItem() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid); intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.HORIZONTAL; mNumRows = 3; initActivity(intent); final View lastView = mGridView.findViewHolderForAdapterPosition(5).itemView; mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.getItemAnimator().setRemoveDuration(20000); mActivity.removeItems(5, 1); } }); waitForItemAnimationStart(); AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView); mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null, lastView, info); assertNoCollectionItemInfo(info); } static class FiveViewTypesProvider implements ViewTypeProvider { @Override public int getViewType(int position) { switch (position) { case 0: return 0; case 1: return 1; case 2: return 2; case 3: return 3; case 4: return 4; } return 199; } } // Used by testItemAlignmentVertical() testItemAlignmentHorizontal() static class ItemAlignmentWithPaddingFacetProvider implements ItemAlignmentFacetProvider { final ItemAlignmentFacet mFacet0; final ItemAlignmentFacet mFacet1; final ItemAlignmentFacet mFacet2; final ItemAlignmentFacet mFacet3; final ItemAlignmentFacet mFacet4; ItemAlignmentWithPaddingFacetProvider() { ItemAlignmentFacet.ItemAlignmentDef[] defs; mFacet0 = new ItemAlignmentFacet(); defs = new ItemAlignmentFacet.ItemAlignmentDef[1]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t1); defs[0].setItemAlignmentOffsetPercent(0); defs[0].setItemAlignmentOffsetWithPadding(false); mFacet0.setAlignmentDefs(defs); mFacet1 = new ItemAlignmentFacet(); defs = new ItemAlignmentFacet.ItemAlignmentDef[1]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t1); defs[0].setItemAlignmentOffsetPercent(0); defs[0].setItemAlignmentOffsetWithPadding(true); mFacet1.setAlignmentDefs(defs); mFacet2 = new ItemAlignmentFacet(); defs = new ItemAlignmentFacet.ItemAlignmentDef[1]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t2); defs[0].setItemAlignmentOffsetPercent(100); defs[0].setItemAlignmentOffsetWithPadding(true); mFacet2.setAlignmentDefs(defs); mFacet3 = new ItemAlignmentFacet(); defs = new ItemAlignmentFacet.ItemAlignmentDef[1]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t2); defs[0].setItemAlignmentOffsetPercent(50); defs[0].setItemAlignmentOffsetWithPadding(true); mFacet3.setAlignmentDefs(defs); mFacet4 = new ItemAlignmentFacet(); defs = new ItemAlignmentFacet.ItemAlignmentDef[1]; defs[0] = new ItemAlignmentFacet.ItemAlignmentDef(); defs[0].setItemAlignmentViewId(R.id.t2); defs[0].setItemAlignmentOffsetPercent(50); defs[0].setItemAlignmentOffsetWithPadding(false); mFacet4.setAlignmentDefs(defs); } @Override public ItemAlignmentFacet getItemAlignmentFacet(int viewType) { switch (viewType) { case 0: return mFacet0; case 1: return mFacet1; case 2: return mFacet2; case 3: return mFacet3; case 4: return mFacet4; } return null; } } @Test public void testItemAlignmentVertical() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout2); int[] items = new int[5]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, FiveViewTypesProvider.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS, ItemAlignmentWithPaddingFacetProvider.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE); mGridView.setWindowAlignmentOffsetPercent(50); mGridView.setWindowAlignmentOffset(0); } }); waitForLayout(); final float windowAlignCenter = mGridView.getHeight() / 2f; Rect rect = new Rect(); View textView; // test 1: does not include padding textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1); rect.set(0, 0, textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.top, DELTA); // test 2: including low padding setSelectedPosition(1); textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1); assertTrue(textView.getPaddingTop() > 0); rect.set(0, textView.getPaddingTop(), textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.top, DELTA); // test 3: including high padding setSelectedPosition(2); textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingBottom() > 0); rect.set(0, 0, textView.getWidth(), textView.getHeight() - textView.getPaddingBottom()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.bottom, DELTA); // test 4: including padding will be ignored if offsetPercent is not 0 or 100 setSelectedPosition(3); textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingTop() != textView.getPaddingBottom()); rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.bottom, DELTA); // test 5: does not include padding setSelectedPosition(4); textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingTop() != textView.getPaddingBottom()); rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.bottom, DELTA); } @Test public void testItemAlignmentHorizontal() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3); int[] items = new int[5]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, FiveViewTypesProvider.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS, ItemAlignmentWithPaddingFacetProvider.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE); mGridView.setWindowAlignmentOffsetPercent(50); mGridView.setWindowAlignmentOffset(0); } }); waitForLayout(); final float windowAlignCenter = mGridView.getWidth() / 2f; Rect rect = new Rect(); View textView; // test 1: does not include padding textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1); rect.set(0, 0, textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.left, DELTA); // test 2: including low padding setSelectedPosition(1); textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1); assertTrue(textView.getPaddingLeft() > 0); rect.set(textView.getPaddingLeft(), 0, textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.left, DELTA); // test 3: including high padding setSelectedPosition(2); textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingRight() > 0); rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); // test 4: including padding will be ignored if offsetPercent is not 0 or 100 setSelectedPosition(3); textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingLeft() != textView.getPaddingRight()); rect.set(0, 0, textView.getWidth() / 2, textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); // test 5: does not include padding setSelectedPosition(4); textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingLeft() != textView.getPaddingRight()); rect.set(0, 0, textView.getWidth() / 2, textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); } @Test public void testItemAlignmentHorizontalRtl() throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear); intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3); int[] items = new int[5]; for (int i = 0; i < items.length; i++) { items[i] = 300; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS, FiveViewTypesProvider.class.getName()); intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS, ItemAlignmentWithPaddingFacetProvider.class.getName()); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE); mGridView.setWindowAlignmentOffsetPercent(50); mGridView.setWindowAlignmentOffset(0); } }); waitForLayout(); final float windowAlignCenter = mGridView.getWidth() / 2f; Rect rect = new Rect(); View textView; // test 1: does not include padding textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1); rect.set(0, 0, textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); // test 2: including low padding setSelectedPosition(1); textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1); assertTrue(textView.getPaddingRight() > 0); rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); // test 3: including high padding setSelectedPosition(2); textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingLeft() > 0); rect.set(textView.getPaddingLeft(), 0, textView.getWidth(), textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.left, DELTA); // test 4: including padding will be ignored if offsetPercent is not 0 or 100 setSelectedPosition(3); textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingLeft() != textView.getPaddingRight()); rect.set(0, 0, textView.getWidth() / 2, textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); // test 5: does not include padding setSelectedPosition(4); textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2); assertTrue(textView.getPaddingLeft() != textView.getPaddingRight()); rect.set(0, 0, textView.getWidth() / 2, textView.getHeight()); mGridView.offsetDescendantRectToMyCoords(textView, rect); assertEquals(windowAlignCenter, rect.right, DELTA); } enum ItemLocation { ITEM_AT_LOW, ITEM_AT_KEY_LINE, ITEM_AT_HIGH }; static class ItemAt { final int mScrollPosition; final int mPosition; final ItemLocation mLocation; ItemAt(int scrollPosition, int position, ItemLocation loc) { mScrollPosition = scrollPosition; mPosition = position; mLocation = loc; } ItemAt(int position, ItemLocation loc) { mScrollPosition = position; mPosition = position; mLocation = loc; } } /** * When scroll to position, item at position is expected at given location. */ static ItemAt itemAt(int position, ItemLocation location) { return new ItemAt(position, location); } /** * When scroll to scrollPosition, item at position is expected at given location. */ static ItemAt itemAt(int scrollPosition, int position, ItemLocation location) { return new ItemAt(scrollPosition, position, location); } void prepareKeyLineTest(int numItems) throws Throwable { Intent intent = new Intent(); intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear); int[] items = new int[numItems]; for (int i = 0; i < items.length; i++) { items[i] = 32; } intent.putExtra(GridActivity.EXTRA_ITEMS, items); intent.putExtra(GridActivity.EXTRA_STAGGERED, false); mOrientation = BaseGridView.VERTICAL; mNumRows = 1; initActivity(intent); } public void testPreferKeyLine(final int windowAlignment, final boolean preferKeyLineOverLow, final boolean preferKeyLineOverHigh, ItemLocation assertFirstItemLocation, ItemLocation assertLastItemLocation) throws Throwable { testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh, itemAt(0, assertFirstItemLocation), itemAt(mActivity.mNumItems - 1, assertLastItemLocation)); } public void testPreferKeyLine(final int windowAlignment, final boolean preferKeyLineOverLow, final boolean preferKeyLineOverHigh, ItemLocation assertFirstItemLocation, ItemAt assertLastItemLocation) throws Throwable { testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh, itemAt(0, assertFirstItemLocation), assertLastItemLocation); } public void testPreferKeyLine(final int windowAlignment, final boolean preferKeyLineOverLow, final boolean preferKeyLineOverHigh, ItemAt assertFirstItemLocation, ItemLocation assertLastItemLocation) throws Throwable { testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh, assertFirstItemLocation, itemAt(mActivity.mNumItems - 1, assertLastItemLocation)); } public void testPreferKeyLine(final int windowAlignment, final boolean preferKeyLineOverLow, final boolean preferKeyLineOverHigh, ItemAt assertFirstItemLocation, ItemAt assertLastItemLocation) throws Throwable { startWaitLayout(); mActivityTestRule.runOnUiThread(new Runnable() { @Override public void run() { mGridView.setWindowAlignment(windowAlignment); mGridView.setWindowAlignmentOffsetPercent(50); mGridView.setWindowAlignmentOffset(0); mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(preferKeyLineOverLow); mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(preferKeyLineOverHigh); } }); waitForLayout(); final int lowPadding = mGridView.getPaddingTop(); final int highPadding = mGridView.getHeight() - mGridView.getPaddingBottom(); final int windowAlignCenter = mGridView.getHeight() / 2; setSelectedPosition(assertFirstItemLocation.mScrollPosition); View view = mGridView.findViewHolderForAdapterPosition(assertFirstItemLocation.mPosition) .itemView; switch (assertFirstItemLocation.mLocation) { case ITEM_AT_LOW: assertEquals(lowPadding, view.getTop()); break; case ITEM_AT_HIGH: assertEquals(highPadding, view.getBottom()); break; case ITEM_AT_KEY_LINE: assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA); break; } setSelectedPosition(assertLastItemLocation.mScrollPosition); view = mGridView.findViewHolderForAdapterPosition(assertLastItemLocation.mPosition) .itemView; switch (assertLastItemLocation.mLocation) { case ITEM_AT_LOW: assertEquals(lowPadding, view.getTop()); break; case ITEM_AT_HIGH: assertEquals(highPadding, view.getBottom()); break; case ITEM_AT_KEY_LINE: assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA); break; } } @Test public void testPreferKeyLine1() throws Throwable { prepareKeyLineTest(1); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false, ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false, ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); } @Test public void testPreferKeyLine2() throws Throwable { prepareKeyLineTest(2); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false, ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true, ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false, itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true, itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false, itemAt(0, 1, ItemLocation.ITEM_AT_HIGH), itemAt(1, 1, ItemLocation.ITEM_AT_HIGH)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true, itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false, itemAt(0, 1, ItemLocation.ITEM_AT_HIGH), itemAt(1, 1, ItemLocation.ITEM_AT_HIGH)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true, itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false, ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true, ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false, itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE)); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true, itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE), itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE)); } @Test public void testPreferKeyLine10000() throws Throwable { prepareKeyLineTest(10000); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true, ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH); testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true, ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH); } }