GridWidgetTest.java revision 38b880c10a1169c558c1b952e36d330780f21ba3
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package androidx.leanback.widget;
17
18import static org.junit.Assert.assertEquals;
19import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertNotNull;
21import static org.junit.Assert.assertNotSame;
22import static org.junit.Assert.assertNull;
23import static org.junit.Assert.assertSame;
24import static org.junit.Assert.assertTrue;
25import static org.mockito.Mockito.any;
26import static org.mockito.Mockito.anyInt;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.timeout;
29import static org.mockito.Mockito.times;
30import static org.mockito.Mockito.verify;
31
32import android.content.Intent;
33import android.graphics.Canvas;
34import android.graphics.Color;
35import android.graphics.Rect;
36import android.graphics.drawable.ColorDrawable;
37import android.os.Build;
38import android.os.Parcelable;
39import android.support.test.InstrumentationRegistry;
40import android.support.test.filters.LargeTest;
41import android.support.test.filters.SdkSuppress;
42import android.support.test.rule.ActivityTestRule;
43import android.support.test.runner.AndroidJUnit4;
44import androidx.leanback.test.R;
45import androidx.leanback.testutils.PollingCheck;
46import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
47import androidx.recyclerview.widget.DefaultItemAnimator;
48import androidx.recyclerview.widget.RecyclerView;
49import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
50import android.text.Selection;
51import android.text.Spannable;
52import android.util.DisplayMetrics;
53import android.util.SparseArray;
54import android.util.SparseIntArray;
55import android.util.TypedValue;
56import android.view.KeyEvent;
57import android.view.View;
58import android.view.ViewGroup;
59import android.widget.TextView;
60
61import org.junit.After;
62import org.junit.Rule;
63import org.junit.Test;
64import org.junit.rules.TestName;
65import org.junit.runner.RunWith;
66import org.mockito.Mockito;
67
68import java.util.ArrayList;
69import java.util.Arrays;
70import java.util.Comparator;
71import java.util.HashMap;
72import java.util.HashSet;
73
74@LargeTest
75@RunWith(AndroidJUnit4.class)
76public class GridWidgetTest {
77
78    private static final float DELTA = 1f;
79    private static final boolean HUMAN_DELAY = false;
80    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
81    private static final int WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS = 2000;
82    private static final int WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS = 6000;
83
84    protected ActivityTestRule<GridActivity> mActivityTestRule;
85    protected GridActivity mActivity;
86    protected BaseGridView mGridView;
87    protected GridLayoutManager mLayoutManager;
88    private GridLayoutManager.OnLayoutCompleteListener mWaitLayoutListener;
89    protected int mOrientation;
90    protected int mNumRows;
91    protected int[] mRemovedItems;
92
93    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
94        @Override
95        public int compare(View lhs, View rhs) {
96            if (mOrientation == BaseGridView.HORIZONTAL) {
97                return lhs.getLeft() - rhs.getLeft();
98            } else {
99                return lhs.getTop() - rhs.getTop();
100            }
101        };
102    };
103
104    /**
105     * Verify margins between items on same row are same.
106     */
107    private final Runnable mVerifyLayout = new Runnable() {
108        @Override
109        public void run() {
110            verifyMargin();
111        }
112    };
113
114    @Rule public TestName testName = new TestName();
115
116    public static void sendKey(int keyCode) {
117        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
118    }
119
120    public static void sendRepeatedKeys(int repeats, int keyCode) {
121        for (int i = 0; i < repeats; i++) {
122            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
123        }
124    }
125
126    private void humanDelay(int delay) throws InterruptedException {
127        if (HUMAN_DELAY) Thread.sleep(delay);
128    }
129    /**
130     * Change size of the Adapter and notifyDataSetChanged.
131     */
132    private void changeArraySize(final int size) throws Throwable {
133        performAndWaitForAnimation(new Runnable() {
134            @Override
135            public void run() {
136                mActivity.changeArraySize(size);
137            }
138        });
139    }
140
141    static String dumpGridView(BaseGridView gridView) {
142        return "findFocus:" + gridView.getRootView().findFocus()
143                + " isLayoutRequested:" + gridView.isLayoutRequested()
144                + " selectedPosition:" + gridView.getSelectedPosition()
145                + " adapter.itemCount:" + gridView.getAdapter().getItemCount()
146                + " itemAnimator.isRunning:" + gridView.getItemAnimator().isRunning()
147                + " scrollState:" + gridView.getScrollState();
148    }
149
150    /**
151     * Change selected position.
152     */
153    private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable {
154        startWaitLayout();
155        mActivityTestRule.runOnUiThread(new Runnable() {
156            @Override
157            public void run() {
158                mGridView.setSelectedPosition(position, scrollExtra);
159            }
160        });
161        waitForLayout(false);
162    }
163
164    private void setSelectedPosition(final int position) throws Throwable {
165        setSelectedPosition(position, 0);
166    }
167
168    private void setSelectedPositionSmooth(final int position) throws Throwable {
169        mActivityTestRule.runOnUiThread(new Runnable() {
170            @Override
171            public void run() {
172                mGridView.setSelectedPositionSmooth(position);
173            }
174        });
175    }
176    /**
177     * Scrolls using given key.
178     */
179    protected void scroll(int key, Runnable verify) throws Throwable {
180        do {
181            if (verify != null) {
182                mActivityTestRule.runOnUiThread(verify);
183            }
184            sendRepeatedKeys(10, key);
185            try {
186                Thread.sleep(300);
187            } catch (InterruptedException ex) {
188                break;
189            }
190        } while (mGridView.getLayoutManager().isSmoothScrolling()
191                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
192    }
193
194    protected void scrollToBegin(Runnable verify) throws Throwable {
195        int key;
196        // first move to first column/row
197        if (mOrientation == BaseGridView.HORIZONTAL) {
198            key = KeyEvent.KEYCODE_DPAD_UP;
199        } else {
200            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
201                key = KeyEvent.KEYCODE_DPAD_RIGHT;
202            } else {
203                key = KeyEvent.KEYCODE_DPAD_LEFT;
204            }
205        }
206        scroll(key, null);
207        if (mOrientation == BaseGridView.HORIZONTAL) {
208            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
209                key = KeyEvent.KEYCODE_DPAD_RIGHT;
210            } else {
211                key = KeyEvent.KEYCODE_DPAD_LEFT;
212            }
213        } else {
214            key = KeyEvent.KEYCODE_DPAD_UP;
215        }
216        scroll(key, verify);
217    }
218
219    protected void scrollToEnd(Runnable verify) throws Throwable {
220        int key;
221        // first move to first column/row
222        if (mOrientation == BaseGridView.HORIZONTAL) {
223            key = KeyEvent.KEYCODE_DPAD_UP;
224        } else {
225            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
226                key = KeyEvent.KEYCODE_DPAD_RIGHT;
227            } else {
228                key = KeyEvent.KEYCODE_DPAD_LEFT;
229            }
230        }
231        scroll(key, null);
232        if (mOrientation == BaseGridView.HORIZONTAL) {
233            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
234                key = KeyEvent.KEYCODE_DPAD_LEFT;
235            } else {
236                key = KeyEvent.KEYCODE_DPAD_RIGHT;
237            }
238        } else {
239            key = KeyEvent.KEYCODE_DPAD_DOWN;
240        }
241        scroll(key, verify);
242    }
243
244    /**
245     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
246     */
247    protected View[][] sortByRows() {
248        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
249        ArrayList<Integer> rowLocations = new ArrayList<>();
250        for (int i = 0; i < mGridView.getChildCount(); i++) {
251            View v = mGridView.getChildAt(i);
252            int rowLocation;
253            if (mOrientation == BaseGridView.HORIZONTAL) {
254                rowLocation = v.getTop();
255            } else {
256                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL
257                        ? v.getRight() : v.getLeft();
258            }
259            ArrayList<View> views = rows.get(rowLocation);
260            if (views == null) {
261                views = new ArrayList<View>();
262                rows.put(rowLocation, views);
263                rowLocations.add(rowLocation);
264            }
265            views.add(v);
266        }
267        Object[] sortedLocations = rowLocations.toArray();
268        Arrays.sort(sortedLocations);
269        if (mNumRows != rows.size()) {
270            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
271        }
272        View[][] sorted = new View[rows.size()][];
273        for (int i = 0; i < rowLocations.size(); i++) {
274            Integer rowLocation = rowLocations.get(i);
275            ArrayList<View> arr = rows.get(rowLocation);
276            View[] views = arr.toArray(new View[arr.size()]);
277            Arrays.sort(views, mRowSortComparator);
278            sorted[i] = views;
279        }
280        return sorted;
281    }
282
283    protected void verifyMargin() {
284        View[][] sorted = sortByRows();
285        for (int row = 0; row < sorted.length; row++) {
286            View[] views = sorted[row];
287            int margin = -1;
288            for (int i = 1; i < views.length; i++) {
289                if (mOrientation == BaseGridView.HORIZONTAL) {
290                    assertEquals(mGridView.getHorizontalMargin(),
291                            views[i].getLeft() - views[i - 1].getRight());
292                } else {
293                    assertEquals(mGridView.getVerticalMargin(),
294                            views[i].getTop() - views[i - 1].getBottom());
295                }
296            }
297        }
298    }
299
300    protected void verifyBeginAligned() {
301        View[][] sorted = sortByRows();
302        int alignedLocation = 0;
303        if (mOrientation == BaseGridView.HORIZONTAL) {
304            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
305                for (int i = 0; i < sorted.length; i++) {
306                    if (i == 0) {
307                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
308                    } else {
309                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
310                    }
311                }
312            } else {
313                for (int i = 0; i < sorted.length; i++) {
314                    if (i == 0) {
315                        alignedLocation = sorted[i][0].getLeft();
316                    } else {
317                        assertEquals(alignedLocation, sorted[i][0].getLeft());
318                    }
319                }
320            }
321        } else {
322            for (int i = 0; i < sorted.length; i++) {
323                if (i == 0) {
324                    alignedLocation = sorted[i][0].getTop();
325                } else {
326                    assertEquals(alignedLocation, sorted[i][0].getTop());
327                }
328            }
329        }
330    }
331
332    protected int[] getEndEdges() {
333        View[][] sorted = sortByRows();
334        int[] edges = new int[sorted.length];
335        if (mOrientation == BaseGridView.HORIZONTAL) {
336            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
337                for (int i = 0; i < sorted.length; i++) {
338                    edges[i] = sorted[i][0].getLeft();
339                }
340            } else {
341                for (int i = 0; i < sorted.length; i++) {
342                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
343                }
344            }
345        } else {
346            for (int i = 0; i < sorted.length; i++) {
347                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
348            }
349        }
350        return edges;
351    }
352
353    protected void verifyEdgesSame(int[] edges, int[] edges2) {
354        assertEquals(edges.length, edges2.length);
355        for (int i = 0; i < edges.length; i++) {
356            assertEquals(edges[i], edges2[i]);
357        }
358    }
359
360    protected void verifyBoundCount(int count) {
361        if (mActivity.getBoundCount() != count) {
362            StringBuffer b = new StringBuffer();
363            b.append("ItemsLength: ");
364            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
365                b.append(mActivity.mItemLengths[i]).append(",");
366            }
367            assertEquals("Bound count does not match, ItemsLengths: "+ b,
368                    count, mActivity.getBoundCount());
369        }
370    }
371
372    private static int getCenterY(View v) {
373        return (v.getTop() + v.getBottom())/2;
374    }
375
376    private static int getCenterX(View v) {
377        return (v.getLeft() + v.getRight())/2;
378    }
379
380    private void initActivity(Intent intent) throws Throwable {
381        mActivityTestRule = new ActivityTestRule<GridActivity>(GridActivity.class, false, false);
382        mActivity = mActivityTestRule.launchActivity(intent);
383        mActivityTestRule.runOnUiThread(new Runnable() {
384                @Override
385                public void run() {
386                    mActivity.setTitle(testName.getMethodName());
387                }
388            });
389        Thread.sleep(1000);
390        mGridView = mActivity.mGridView;
391        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
392    }
393
394    @After
395    public void clearTest() {
396        mWaitLayoutListener = null;
397        mLayoutManager = null;
398        mGridView = null;
399        mActivity = null;
400        mActivityTestRule = null;
401    }
402
403    /**
404     * Must be called before waitForLayout() to prepare layout listener.
405     */
406    protected void startWaitLayout() {
407        if (mWaitLayoutListener != null) {
408            throw new IllegalStateException("startWaitLayout() already called");
409        }
410        if (mLayoutManager.mLayoutCompleteListener != null) {
411            throw new IllegalStateException("Cannot startWaitLayout()");
412        }
413        mWaitLayoutListener = mLayoutManager.mLayoutCompleteListener =
414                mock(GridLayoutManager.OnLayoutCompleteListener.class);
415    }
416
417    /**
418     * wait layout to be called and remove the listener.
419     */
420    protected void waitForLayout() {
421        waitForLayout(true);
422    }
423
424    /**
425     * wait layout to be called and remove the listener.
426     * @param force True if always wait regardless if layout requested
427     */
428    protected void waitForLayout(boolean force) {
429        if (mWaitLayoutListener == null) {
430            throw new IllegalStateException("startWaitLayout() not called");
431        }
432        if (mWaitLayoutListener != mLayoutManager.mLayoutCompleteListener) {
433            throw new IllegalStateException("layout listener inconistent");
434        }
435        try {
436            if (force || mGridView.isLayoutRequested()) {
437                verify(mWaitLayoutListener, timeout(WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS).atLeastOnce())
438                        .onLayoutCompleted(any(RecyclerView.State.class));
439            }
440        } finally {
441            mWaitLayoutListener = null;
442            mLayoutManager.mLayoutCompleteListener = null;
443        }
444    }
445
446    /**
447     * If currently running animator, wait for it to finish, otherwise return immediately.
448     * To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has
449     * processed adapter change.
450     */
451    protected void waitForItemAnimation(int timeoutMs) throws Throwable {
452        final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
453                RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class);
454        mActivityTestRule.runOnUiThread(new Runnable() {
455            @Override
456            public void run() {
457                mGridView.getItemAnimator().isRunning(listener);
458            }
459        });
460        verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished();
461    }
462
463    protected void waitForItemAnimation() throws Throwable {
464        waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS);
465    }
466
467    /**
468     * wait animation start
469     */
470    protected void waitForItemAnimationStart() throws Throwable {
471        long totalWait = 0;
472        while (!mGridView.getItemAnimator().isRunning()) {
473            Thread.sleep(10);
474            if ((totalWait += 10) > WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS) {
475                throw new RuntimeException("waitForItemAnimationStart Timeout");
476            }
477        }
478    }
479
480    /**
481     * Run task in UI thread and wait for layout and ItemAnimator finishes.
482     */
483    protected void performAndWaitForAnimation(Runnable task) throws Throwable {
484        startWaitLayout();
485        mActivityTestRule.runOnUiThread(task);
486        waitForLayout();
487        waitForItemAnimation();
488    }
489
490    protected void waitForScrollIdle() throws Throwable {
491        waitForScrollIdle(null);
492    }
493
494    /**
495     * Wait for grid view stop scroll and optionally verify state of grid view.
496     */
497    protected void waitForScrollIdle(Runnable verify) throws Throwable {
498        Thread.sleep(100);
499        int total = 0;
500        while (mGridView.getLayoutManager().isSmoothScrolling()
501                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
502            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
503                throw new RuntimeException("waitForScrollIdle Timeout");
504            }
505            try {
506                Thread.sleep(100);
507            } catch (InterruptedException ex) {
508                break;
509            }
510            if (verify != null) {
511                mActivityTestRule.runOnUiThread(verify);
512            }
513        }
514    }
515
516    @Test
517    public void testThreeRowHorizontalBasic() throws Throwable {
518        Intent intent = new Intent();
519        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
520        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
521        initActivity(intent);
522        mOrientation = BaseGridView.HORIZONTAL;
523        mNumRows = 3;
524
525        scrollToEnd(mVerifyLayout);
526
527        scrollToBegin(mVerifyLayout);
528
529        verifyBeginAligned();
530    }
531
532    static class DividerDecoration extends RecyclerView.ItemDecoration {
533
534        private ColorDrawable mTopDivider;
535        private ColorDrawable mBottomDivider;
536        private int mLeftOffset;
537        private int mRightOffset;
538        private int mTopOffset;
539        private int mBottomOffset;
540
541        DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
542            mLeftOffset = leftOffset;
543            mTopOffset = topOffset;
544            mRightOffset = rightOffset;
545            mBottomOffset = bottomOffset;
546        }
547
548        @Override
549        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
550            if (mTopDivider == null) {
551                mTopDivider = new ColorDrawable(Color.RED);
552            }
553            if (mBottomDivider == null) {
554                mBottomDivider = new ColorDrawable(Color.BLUE);
555            }
556            final int childCount = parent.getChildCount();
557            final int width = parent.getWidth();
558            for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
559                final View view = parent.getChildAt(childViewIndex);
560                mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY());
561                mTopDivider.draw(c);
562                mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width,
563                        (int) view.getY() + view.getHeight() + mBottomOffset);
564                mBottomDivider.draw(c);
565            }
566        }
567
568        @Override
569        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
570                                   RecyclerView.State state) {
571            outRect.left = mLeftOffset;
572            outRect.top = mTopOffset;
573            outRect.right = mRightOffset;
574            outRect.bottom = mBottomOffset;
575        }
576    }
577
578    @Test
579    public void testItemDecorationAndMargins() throws Throwable {
580
581        final int leftMargin = 3;
582        final int topMargin = 4;
583        final int rightMargin = 7;
584        final int bottomMargin = 8;
585        final int itemHeight = 100;
586
587        Intent intent = new Intent();
588        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
589        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
590        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
591                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
592        initActivity(intent);
593        mOrientation = BaseGridView.VERTICAL;
594        mNumRows = 1;
595
596        final int paddingLeft = mGridView.getPaddingLeft();
597        final int paddingTop = mGridView.getPaddingTop();
598        final int verticalSpace = mGridView.getVerticalMargin();
599        final int decorationLeft = 17;
600        final int decorationTop = 1;
601        final int decorationRight = 19;
602        final int decorationBottom = 2;
603
604        performAndWaitForAnimation(new Runnable() {
605            @Override
606            public void run() {
607                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
608                        decorationRight, decorationBottom));
609            }
610        });
611
612        View child0 = mGridView.getChildAt(0);
613        View child1 = mGridView.getChildAt(1);
614        View child2 = mGridView.getChildAt(2);
615
616        assertEquals(itemHeight, child0.getBottom() - child0.getTop());
617
618        // verify left margins
619        assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft());
620        assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft());
621        assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft());
622        // verify top bottom margins and decoration offset
623        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop());
624        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
625                child1.getTop() - child0.getBottom());
626        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
627                child2.getTop() - child1.getBottom());
628
629    }
630
631    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
632    @Test
633    public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
634        final int leftMargin = 3;
635        final int topMargin = 4;
636        final int rightMargin = 7;
637        final int bottomMargin = 8;
638        final int itemHeight = 100;
639        final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
640
641        Intent intent = new Intent();
642        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
643        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
644        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
645        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
646                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
647        intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
648        initActivity(intent);
649        mOrientation = BaseGridView.VERTICAL;
650        mNumRows = 1;
651
652        final int paddingLeft = mGridView.getPaddingLeft();
653        final int paddingTop = mGridView.getPaddingTop();
654        final int verticalSpace = mGridView.getVerticalMargin();
655        final int decorationLeft = 17;
656        final int decorationTop = 1;
657        final int decorationRight = 19;
658        final int decorationBottom = 2;
659
660        final Rect opticalPaddings = new Rect();
661        mGridView.getResources().getDrawable(ninePatchDrawableResourceId)
662                .getPadding(opticalPaddings);
663        final int opticalInsetsLeft = opticalPaddings.left;
664        final int opticalInsetsTop = opticalPaddings.top;
665        final int opticalInsetsRight = opticalPaddings.right;
666        final int opticalInsetsBottom = opticalPaddings.bottom;
667        assertTrue(opticalInsetsLeft > 0);
668        assertTrue(opticalInsetsTop > 0);
669        assertTrue(opticalInsetsRight > 0);
670        assertTrue(opticalInsetsBottom > 0);
671
672        performAndWaitForAnimation(new Runnable() {
673            @Override
674            public void run() {
675                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
676                        decorationRight, decorationBottom));
677            }
678        });
679
680        View child0 = mGridView.getChildAt(0);
681        View child1 = mGridView.getChildAt(1);
682        View child2 = mGridView.getChildAt(2);
683
684        assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
685                child0.getBottom() - child0.getTop());
686
687        // verify left margins decoration and optical insets
688        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
689                child0.getLeft());
690        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
691                child1.getLeft());
692        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
693                child2.getLeft());
694        // verify top bottom margins decoration offset and optical insets
695        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
696        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
697                (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
698        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
699                (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
700
701    }
702
703    @Test
704    public void testThreeColumnVerticalBasic() throws Throwable {
705
706        Intent intent = new Intent();
707        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid);
708        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
709        initActivity(intent);
710        mOrientation = BaseGridView.VERTICAL;
711        mNumRows = 3;
712
713        scrollToEnd(mVerifyLayout);
714
715        scrollToBegin(mVerifyLayout);
716
717        verifyBeginAligned();
718    }
719
720    @Test
721    public void testRedundantAppendRemove() throws Throwable {
722        Intent intent = new Intent();
723        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
724                R.layout.vertical_grid_testredundantappendremove);
725        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
726                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
727                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
728                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
729                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
730                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
731                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
732                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
733                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
734                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
735                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
736        initActivity(intent);
737        mOrientation = BaseGridView.VERTICAL;
738        mNumRows = 3;
739
740        scrollToEnd(mVerifyLayout);
741
742        scrollToBegin(mVerifyLayout);
743
744        verifyBeginAligned();
745    }
746
747    @Test
748    public void testRedundantAppendRemove2() throws Throwable {
749        Intent intent = new Intent();
750        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
751                R.layout.horizontal_grid_testredundantappendremove2);
752        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
753                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
754                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
755                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
756                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
757                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
758        initActivity(intent);
759        mOrientation = BaseGridView.HORIZONTAL;
760        mNumRows = 3;
761        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
762
763        // test append without staggered result cache
764        scrollToEnd(mVerifyLayout);
765
766        int[] endEdges = getEndEdges();
767
768        scrollToBegin(mVerifyLayout);
769
770        verifyBeginAligned();
771
772        // now test append with staggered result cache
773        changeArraySize(3);
774        assertEquals("Staggerd cache should be kept as is when no item size change",
775                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
776
777        changeArraySize(100);
778
779        scrollToEnd(mVerifyLayout);
780
781        // we should get same aligned end edges
782        int[] endEdges2 = getEndEdges();
783        verifyEdgesSame(endEdges, endEdges2);
784    }
785
786
787    @Test
788    public void testLayoutWhenAViewIsInvalidated() throws Throwable {
789        Intent intent = new Intent();
790        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
791        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
792        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
793        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
794        mNumRows = 1;
795        initActivity(intent);
796        mOrientation = BaseGridView.VERTICAL;
797        waitOneUiCycle();
798
799        // push views to cache.
800        mActivityTestRule.runOnUiThread(new Runnable() {
801            @Override
802            public void run() {
803                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] * 3;
804                mActivity.mGridView.getAdapter().notifyItemChanged(0);
805            }
806        });
807        waitForItemAnimation();
808
809        // notifyDataSetChange will mark the cached views FLAG_INVALID
810        mActivityTestRule.runOnUiThread(new Runnable() {
811            @Override
812            public void run() {
813                mActivity.mGridView.getAdapter().notifyDataSetChanged();
814            }
815        });
816        waitForItemAnimation();
817
818        // Cached views will be added in prelayout with FLAG_INVALID, in post layout we should
819        // handle it properly
820        mActivityTestRule.runOnUiThread(new Runnable() {
821            @Override
822            public void run() {
823                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] / 3;
824                mActivity.mGridView.getAdapter().notifyItemChanged(0);
825            }
826        });
827
828        waitForItemAnimation();
829    }
830
831    @Test
832    public void testWrongInsertViewIndexInFastRelayout() throws Throwable {
833        Intent intent = new Intent();
834        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
835        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
836        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
837        mNumRows = 1;
838        initActivity(intent);
839        mOrientation = BaseGridView.VERTICAL;
840
841        // removing two children, they will be hidden views as first 2 children of RV.
842        mActivityTestRule.runOnUiThread(new Runnable() {
843            @Override
844            public void run() {
845                mGridView.getItemAnimator().setRemoveDuration(2000);
846                mActivity.removeItems(0, 2);
847            }
848        });
849        waitForItemAnimationStart();
850
851        // add three views and notify change of the first item.
852        startWaitLayout();
853        mActivityTestRule.runOnUiThread(new Runnable() {
854            @Override
855            public void run() {
856                mActivity.addItems(0, new int[]{161, 161, 161});
857            }
858        });
859        waitForLayout();
860        startWaitLayout();
861        mActivityTestRule.runOnUiThread(new Runnable() {
862            @Override
863            public void run() {
864                mGridView.getAdapter().notifyItemChanged(0);
865            }
866        });
867        waitForLayout();
868        // after layout, the viewholder should still be the first child of LayoutManager.
869        assertEquals(0, mGridView.getChildAdapterPosition(
870                mGridView.getLayoutManager().getChildAt(0)));
871    }
872
873    @Test
874    public void testMoveIntoPrelayoutItems() throws Throwable {
875        Intent intent = new Intent();
876        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
877        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
878        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
879        mNumRows = 1;
880        initActivity(intent);
881        mOrientation = BaseGridView.VERTICAL;
882
883        final int lastItemPos = mGridView.getChildCount() - 1;
884        assertTrue(mGridView.getChildCount() >= 4);
885        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
886        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
887        mActivityTestRule.runOnUiThread(new Runnable() {
888            @Override
889            public void run() {
890                mGridView.getAdapter().notifyItemChanged(lastItemPos - 3);
891                mGridView.getAdapter().notifyItemChanged(lastItemPos - 2);
892                mGridView.getAdapter().notifyItemChanged(lastItemPos - 1);
893                mActivity.moveItem(900, lastItemPos + 2, true);
894            }
895        });
896        waitForItemAnimation();
897    }
898
899    @Test
900    public void testMoveIntoPrelayoutItems2() throws Throwable {
901        Intent intent = new Intent();
902        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
903        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
904        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
905        mNumRows = 1;
906        initActivity(intent);
907        mOrientation = BaseGridView.VERTICAL;
908
909        setSelectedPosition(999);
910        final int firstItemPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
911        assertTrue(mGridView.getChildCount() >= 4);
912        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
913        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
914        mActivityTestRule.runOnUiThread(new Runnable() {
915            @Override
916            public void run() {
917                mGridView.getAdapter().notifyItemChanged(firstItemPos + 1);
918                mGridView.getAdapter().notifyItemChanged(firstItemPos + 2);
919                mGridView.getAdapter().notifyItemChanged(firstItemPos + 3);
920                mActivity.moveItem(0, firstItemPos - 2, true);
921            }
922        });
923        waitForItemAnimation();
924    }
925
926    void preparePredictiveLayout() throws Throwable {
927        Intent intent = new Intent();
928        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
929        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
930        initActivity(intent);
931        mOrientation = BaseGridView.HORIZONTAL;
932        mNumRows = 1;
933
934        mActivityTestRule.runOnUiThread(new Runnable() {
935            @Override
936            public void run() {
937                mGridView.getItemAnimator().setAddDuration(1000);
938                mGridView.getItemAnimator().setRemoveDuration(1000);
939                mGridView.getItemAnimator().setMoveDuration(1000);
940                mGridView.getItemAnimator().setChangeDuration(1000);
941                mGridView.setSelectedPositionSmooth(50);
942            }
943        });
944        waitForScrollIdle(mVerifyLayout);
945    }
946
947    @Test
948    public void testPredictiveLayoutAdd1() throws Throwable {
949        preparePredictiveLayout();
950        mActivityTestRule.runOnUiThread(new Runnable() {
951            @Override
952            public void run() {
953                mActivity.addItems(51, new int[]{300, 300, 300, 300});
954            }
955        });
956        waitForItemAnimationStart();
957        waitForItemAnimation();
958        assertEquals(50, mGridView.getSelectedPosition());
959        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
960    }
961
962    @Test
963    public void testPredictiveLayoutAdd2() throws Throwable {
964        preparePredictiveLayout();
965        mActivityTestRule.runOnUiThread(new Runnable() {
966            @Override
967            public void run() {
968                mActivity.addItems(50, new int[]{300, 300, 300, 300});
969            }
970        });
971        waitForItemAnimationStart();
972        waitForItemAnimation();
973        assertEquals(54, mGridView.getSelectedPosition());
974        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
975    }
976
977    @Test
978    public void testPredictiveLayoutRemove1() throws Throwable {
979        preparePredictiveLayout();
980        mActivityTestRule.runOnUiThread(new Runnable() {
981            @Override
982            public void run() {
983                mActivity.removeItems(51, 3);
984            }
985        });
986        waitForItemAnimationStart();
987        waitForItemAnimation();
988        assertEquals(50, mGridView.getSelectedPosition());
989        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
990    }
991
992    @Test
993    public void testPredictiveLayoutRemove2() throws Throwable {
994        preparePredictiveLayout();
995        mActivityTestRule.runOnUiThread(new Runnable() {
996            @Override
997            public void run() {
998                mActivity.removeItems(47, 3);
999            }
1000        });
1001        waitForItemAnimationStart();
1002        waitForItemAnimation();
1003        assertEquals(47, mGridView.getSelectedPosition());
1004        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
1005    }
1006
1007    @Test
1008    public void testPredictiveLayoutRemove3() throws Throwable {
1009        preparePredictiveLayout();
1010        mActivityTestRule.runOnUiThread(new Runnable() {
1011            @Override
1012            public void run() {
1013                mActivity.removeItems(0, 51);
1014            }
1015        });
1016        waitForItemAnimationStart();
1017        waitForItemAnimation();
1018        assertEquals(0, mGridView.getSelectedPosition());
1019        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
1020    }
1021
1022    @Test
1023    public void testPredictiveOnMeasureWrapContent() throws Throwable {
1024        Intent intent = new Intent();
1025        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1026                R.layout.horizontal_linear_wrap_content);
1027        int count = 50;
1028        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, count);
1029        initActivity(intent);
1030        mOrientation = BaseGridView.HORIZONTAL;
1031        mNumRows = 1;
1032
1033        waitForScrollIdle(mVerifyLayout);
1034        mActivityTestRule.runOnUiThread(new Runnable() {
1035            @Override
1036            public void run() {
1037                mGridView.setHasFixedSize(false);
1038            }
1039        });
1040
1041        for (int i = 0; i < 30; i++) {
1042            final int oldCount = count;
1043            final int newCount = i;
1044            mActivityTestRule.runOnUiThread(new Runnable() {
1045                @Override
1046                public void run() {
1047                    if (oldCount > 0) {
1048                        mActivity.removeItems(0, oldCount);
1049                    }
1050                    if (newCount > 0) {
1051                        int[] newItems = new int[newCount];
1052                        for (int i = 0; i < newCount; i++) {
1053                            newItems[i] = 400;
1054                        }
1055                        mActivity.addItems(0, newItems);
1056                    }
1057                }
1058            });
1059            waitForItemAnimationStart();
1060            waitForItemAnimation();
1061            count = newCount;
1062        }
1063
1064    }
1065
1066    @Test
1067    public void testPredictiveLayoutRemove4() throws Throwable {
1068        Intent intent = new Intent();
1069        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1070                R.layout.horizontal_grid);
1071        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1072        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1073        initActivity(intent);
1074        mOrientation = BaseGridView.HORIZONTAL;
1075        mNumRows = 3;
1076
1077        mActivityTestRule.runOnUiThread(new Runnable() {
1078            @Override
1079            public void run() {
1080                mGridView.setSelectedPositionSmooth(50);
1081            }
1082        });
1083        waitForScrollIdle();
1084        performAndWaitForAnimation(new Runnable() {
1085            @Override
1086            public void run() {
1087                mActivity.removeItems(0, 49);
1088            }
1089        });
1090        assertEquals(1, mGridView.getSelectedPosition());
1091    }
1092
1093    @Test
1094    public void testPredictiveLayoutRemove5() throws Throwable {
1095        Intent intent = new Intent();
1096        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1097                R.layout.horizontal_grid);
1098        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1099        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
1100        initActivity(intent);
1101        mOrientation = BaseGridView.HORIZONTAL;
1102        mNumRows = 3;
1103
1104        mActivityTestRule.runOnUiThread(new Runnable() {
1105            @Override
1106            public void run() {
1107                mGridView.setSelectedPositionSmooth(50);
1108            }
1109        });
1110        waitForScrollIdle();
1111        performAndWaitForAnimation(new Runnable() {
1112            @Override
1113            public void run() {
1114                mActivity.removeItems(50, 40);
1115            }
1116        });
1117        assertEquals(50, mGridView.getSelectedPosition());
1118        scrollToBegin(mVerifyLayout);
1119        verifyBeginAligned();
1120    }
1121
1122    void waitOneUiCycle() throws Throwable {
1123        mActivityTestRule.runOnUiThread(new Runnable() {
1124            @Override
1125            public void run() {
1126            }
1127        });
1128    }
1129
1130    @Test
1131    public void testDontPruneMovingItem() throws Throwable {
1132        Intent intent = new Intent();
1133        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
1134        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1135        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
1136        initActivity(intent);
1137        mOrientation = BaseGridView.HORIZONTAL;
1138        mNumRows = 1;
1139
1140        mActivityTestRule.runOnUiThread(new Runnable() {
1141            @Override
1142            public void run() {
1143                mGridView.getItemAnimator().setMoveDuration(2000);
1144                mGridView.setSelectedPosition(50);
1145            }
1146        });
1147        waitForScrollIdle();
1148        final ArrayList<RecyclerView.ViewHolder> moveViewHolders = new ArrayList();
1149        for (int i = 51;; i++) {
1150            RecyclerView.ViewHolder vh = mGridView.findViewHolderForAdapterPosition(i);
1151            if (vh == null) {
1152                break;
1153            }
1154            moveViewHolders.add(vh);
1155        }
1156
1157        mActivityTestRule.runOnUiThread(new Runnable() {
1158            @Override
1159            public void run() {
1160                // add a lot of items, so we will push everything to right of 51 out side window
1161                int[] lots_items = new int[1000];
1162                for (int i = 0; i < lots_items.length; i++) {
1163                    lots_items[i] = 300;
1164                }
1165                mActivity.addItems(51, lots_items);
1166            }
1167        });
1168        waitOneUiCycle();
1169        // run a scroll pass, the scroll pass should not remove the animating views even they are
1170        // outside visible areas.
1171        mActivityTestRule.runOnUiThread(new Runnable() {
1172            @Override
1173            public void run() {
1174                mGridView.scrollBy(-3, 0);
1175            }
1176        });
1177        waitOneUiCycle();
1178        for (int i = 0; i < moveViewHolders.size(); i++) {
1179            assertSame(mGridView, moveViewHolders.get(i).itemView.getParent());
1180        }
1181    }
1182
1183    @Test
1184    public void testMoveItemToTheRight() throws Throwable {
1185        Intent intent = new Intent();
1186        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
1187        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1188        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
1189        initActivity(intent);
1190        mOrientation = BaseGridView.HORIZONTAL;
1191        mNumRows = 1;
1192
1193        mActivityTestRule.runOnUiThread(new Runnable() {
1194            @Override
1195            public void run() {
1196                mGridView.getItemAnimator().setAddDuration(2000);
1197                mGridView.getItemAnimator().setMoveDuration(2000);
1198                mGridView.setSelectedPosition(50);
1199            }
1200        });
1201        waitForScrollIdle();
1202        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(51);
1203
1204        int lastPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(
1205                mGridView.getChildCount() - 1));
1206        mActivityTestRule.runOnUiThread(new Runnable() {
1207            @Override
1208            public void run() {
1209                mActivity.moveItem(51, 1000, true);
1210            }
1211        });
1212        final ArrayList<View> moveInViewHolders = new ArrayList();
1213        waitForItemAnimationStart();
1214        mActivityTestRule.runOnUiThread(new Runnable() {
1215            @Override
1216            public void run() {
1217                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
1218                    View v = mGridView.getLayoutManager().getChildAt(i);
1219                    if (mGridView.getChildAdapterPosition(v) >= 51) {
1220                        moveInViewHolders.add(v);
1221                    }
1222                }
1223            }
1224        });
1225        waitOneUiCycle();
1226        assertTrue("prelayout should layout extra items to slide in",
1227                moveInViewHolders.size() > lastPos - 51);
1228        // run a scroll pass, the scroll pass should not remove the animating views even they are
1229        // outside visible areas.
1230        mActivityTestRule.runOnUiThread(new Runnable() {
1231            @Override
1232            public void run() {
1233                mGridView.scrollBy(-3, 0);
1234            }
1235        });
1236        waitOneUiCycle();
1237        for (int i = 0; i < moveInViewHolders.size(); i++) {
1238            assertSame(mGridView, moveInViewHolders.get(i).getParent());
1239        }
1240        assertSame(mGridView, moveViewHolder.itemView.getParent());
1241        assertFalse(moveViewHolder.isRecyclable());
1242        waitForItemAnimation();
1243        assertNull(moveViewHolder.itemView.getParent());
1244        assertTrue(moveViewHolder.isRecyclable());
1245    }
1246
1247    @Test
1248    public void testMoveItemToTheLeft() throws Throwable {
1249        Intent intent = new Intent();
1250        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
1251        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1252        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
1253        initActivity(intent);
1254        mOrientation = BaseGridView.HORIZONTAL;
1255        mNumRows = 1;
1256
1257        mActivityTestRule.runOnUiThread(new Runnable() {
1258            @Override
1259            public void run() {
1260                mGridView.getItemAnimator().setAddDuration(2000);
1261                mGridView.getItemAnimator().setMoveDuration(2000);
1262                mGridView.setSelectedPosition(1500);
1263            }
1264        });
1265        waitForScrollIdle();
1266        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(1499);
1267
1268        int firstPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
1269        mActivityTestRule.runOnUiThread(new Runnable() {
1270            @Override
1271            public void run() {
1272                mActivity.moveItem(1499, 1, true);
1273            }
1274        });
1275        final ArrayList<View> moveInViewHolders = new ArrayList();
1276        waitForItemAnimationStart();
1277        mActivityTestRule.runOnUiThread(new Runnable() {
1278            @Override
1279            public void run() {
1280                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
1281                    View v = mGridView.getLayoutManager().getChildAt(i);
1282                    if (mGridView.getChildAdapterPosition(v) <= 1499) {
1283                        moveInViewHolders.add(v);
1284                    }
1285                }
1286            }
1287        });
1288        waitOneUiCycle();
1289        assertTrue("prelayout should layout extra items to slide in ",
1290                moveInViewHolders.size() > 1499 - firstPos);
1291        // run a scroll pass, the scroll pass should not remove the animating views even they are
1292        // outside visible areas.
1293        mActivityTestRule.runOnUiThread(new Runnable() {
1294            @Override
1295            public void run() {
1296                mGridView.scrollBy(3, 0);
1297            }
1298        });
1299        waitOneUiCycle();
1300        for (int i = 0; i < moveInViewHolders.size(); i++) {
1301            assertSame(mGridView, moveInViewHolders.get(i).getParent());
1302        }
1303        assertSame(mGridView, moveViewHolder.itemView.getParent());
1304        assertFalse(moveViewHolder.isRecyclable());
1305        waitForItemAnimation();
1306        assertNull(moveViewHolder.itemView.getParent());
1307        assertTrue(moveViewHolder.isRecyclable());
1308    }
1309
1310    @Test
1311    public void testContinuousSwapForward() throws Throwable {
1312        Intent intent = new Intent();
1313        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1314                R.layout.horizontal_linear);
1315        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1316        initActivity(intent);
1317        mOrientation = BaseGridView.HORIZONTAL;
1318        mNumRows = 1;
1319
1320        mActivityTestRule.runOnUiThread(new Runnable() {
1321            @Override
1322            public void run() {
1323                mGridView.setSelectedPositionSmooth(150);
1324            }
1325        });
1326        waitForScrollIdle(mVerifyLayout);
1327        for (int i = 150; i < 199; i++) {
1328            final int swapIndex = i;
1329            mActivityTestRule.runOnUiThread(new Runnable() {
1330                @Override
1331                public void run() {
1332                    mActivity.swap(swapIndex, swapIndex + 1);
1333                }
1334            });
1335            Thread.sleep(10);
1336        }
1337        waitForItemAnimation();
1338        assertEquals(199, mGridView.getSelectedPosition());
1339        // check if ItemAnimation finishes at aligned positions:
1340        int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft();
1341        mActivityTestRule.runOnUiThread(new Runnable() {
1342            @Override
1343            public void run() {
1344                mGridView.requestLayout();
1345            }
1346        });
1347        waitForScrollIdle();
1348        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft());
1349    }
1350
1351    @Test
1352    public void testContinuousSwapBackward() throws Throwable {
1353        Intent intent = new Intent();
1354        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1355                R.layout.horizontal_linear);
1356        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1357        initActivity(intent);
1358        mOrientation = BaseGridView.HORIZONTAL;
1359        mNumRows = 1;
1360
1361        mActivityTestRule.runOnUiThread(new Runnable() {
1362            @Override
1363            public void run() {
1364                mGridView.setSelectedPositionSmooth(50);
1365            }
1366        });
1367        waitForScrollIdle(mVerifyLayout);
1368        for (int i = 50; i > 0; i--) {
1369            final int swapIndex = i;
1370            mActivityTestRule.runOnUiThread(new Runnable() {
1371                @Override
1372                public void run() {
1373                    mActivity.swap(swapIndex, swapIndex - 1);
1374                }
1375            });
1376            Thread.sleep(10);
1377        }
1378        waitForItemAnimation();
1379        assertEquals(0, mGridView.getSelectedPosition());
1380        // check if ItemAnimation finishes at aligned positions:
1381        int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft();
1382        mActivityTestRule.runOnUiThread(new Runnable() {
1383            @Override
1384            public void run() {
1385                mGridView.requestLayout();
1386            }
1387        });
1388        waitForScrollIdle();
1389        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft());
1390    }
1391
1392    void testSetSelectedPosition(final boolean inSmoothScroll, final boolean layoutRequested,
1393            final boolean viewVisible, final boolean smooth,
1394            final boolean resultLayoutRequested, final boolean resultSmoothScroller,
1395            final int resultScrollState) throws Throwable {
1396        Intent intent = new Intent();
1397        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
1398        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1500);
1399        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1400        mNumRows = 1;
1401        initActivity(intent);
1402        mOrientation = BaseGridView.VERTICAL;
1403
1404        if (inSmoothScroll) {
1405            setSelectedPositionSmooth(500);
1406        }
1407        mActivityTestRule.runOnUiThread(new Runnable() {
1408            @Override
1409            public void run() {
1410                if (layoutRequested) {
1411                    mGridView.requestLayout();
1412                }
1413                final int position;
1414                if (viewVisible) {
1415                    position = mGridView.getChildAdapterPosition(mGridView.getChildAt(
1416                            mGridView.getChildCount() - 1));
1417                } else {
1418                    position = 1000;
1419                }
1420                if (smooth) {
1421                    mGridView.setSelectedPositionSmooth(position);
1422                } else {
1423                    mGridView.setSelectedPosition(position);
1424                }
1425                assertEquals("isLayoutRequested", resultLayoutRequested,
1426                        mGridView.isLayoutRequested());
1427                assertEquals("isSmoothScrolling", resultSmoothScroller,
1428                        mGridView.getLayoutManager().isSmoothScrolling());
1429                if (!resultSmoothScroller) {
1430                    // getScrollState() only matters when is not running smoothScroller
1431                    assertEquals("getScrollState", resultScrollState,
1432                            mGridView.getScrollState());
1433                }
1434                assertEquals("isLayoutRequested", resultLayoutRequested,
1435                        mGridView.isLayoutRequested());
1436            }
1437        });
1438    }
1439
1440    @Test
1441    public void testSelectedPosition01() throws Throwable {
1442        testSetSelectedPosition(false, false, false, false,
1443                true, false, RecyclerView.SCROLL_STATE_IDLE);
1444    }
1445
1446    @Test
1447    public void testSelectedPosition02() throws Throwable {
1448        testSetSelectedPosition(false, false, false, true,
1449                false, true, RecyclerView.SCROLL_STATE_IDLE);
1450    }
1451
1452    @Test
1453    public void testSelectedPosition03() throws Throwable {
1454        testSetSelectedPosition(false, false, true, false,
1455                false, false, RecyclerView.SCROLL_STATE_IDLE);
1456    }
1457
1458    @Test
1459    public void testSelectedPosition04() throws Throwable {
1460        testSetSelectedPosition(false, false, true, true,
1461                false, false, RecyclerView.SCROLL_STATE_SETTLING);
1462    }
1463
1464    @Test
1465    public void testSelectedPosition05() throws Throwable {
1466        testSetSelectedPosition(false, true, false, false,
1467                true, false, RecyclerView.SCROLL_STATE_IDLE);
1468    }
1469
1470    @Test
1471    public void testSelectedPosition06() throws Throwable {
1472        testSetSelectedPosition(false, true, false, true,
1473                true, false, RecyclerView.SCROLL_STATE_IDLE);
1474    }
1475
1476    @Test
1477    public void testSelectedPosition07() throws Throwable {
1478        testSetSelectedPosition(false, true, true, false,
1479                true, false, RecyclerView.SCROLL_STATE_IDLE);
1480    }
1481
1482    @Test
1483    public void testSelectedPosition08() throws Throwable {
1484        testSetSelectedPosition(false, true, true, true,
1485                true, false, RecyclerView.SCROLL_STATE_IDLE);
1486    }
1487
1488    @Test
1489    public void testSelectedPosition09() throws Throwable {
1490        testSetSelectedPosition(true, false, false, false,
1491                true, false, RecyclerView.SCROLL_STATE_IDLE);
1492    }
1493
1494    @Test
1495    public void testSelectedPosition10() throws Throwable {
1496        testSetSelectedPosition(true, false, false, true,
1497                false, true, RecyclerView.SCROLL_STATE_IDLE);
1498    }
1499
1500    @Test
1501    public void testSelectedPosition11() throws Throwable {
1502        testSetSelectedPosition(true, false, true, false,
1503                false, false, RecyclerView.SCROLL_STATE_IDLE);
1504    }
1505
1506    @Test
1507    public void testSelectedPosition12() throws Throwable {
1508        testSetSelectedPosition(true, false, true, true,
1509                false, true, RecyclerView.SCROLL_STATE_IDLE);
1510    }
1511
1512    @Test
1513    public void testSelectedPosition13() throws Throwable {
1514        testSetSelectedPosition(true, true, false, false,
1515                true, false, RecyclerView.SCROLL_STATE_IDLE);
1516    }
1517
1518    @Test
1519    public void testSelectedPosition14() throws Throwable {
1520        testSetSelectedPosition(true, true, false, true,
1521                true, false, RecyclerView.SCROLL_STATE_IDLE);
1522    }
1523
1524    @Test
1525    public void testSelectedPosition15() throws Throwable {
1526        testSetSelectedPosition(true, true, true, false,
1527                true, false, RecyclerView.SCROLL_STATE_IDLE);
1528    }
1529
1530    @Test
1531    public void testSelectedPosition16() throws Throwable {
1532        testSetSelectedPosition(true, true, true, true,
1533                true, false, RecyclerView.SCROLL_STATE_IDLE);
1534    }
1535
1536    @Test
1537    public void testScrollAndStuck() throws Throwable {
1538        // see b/67370222 fastRelayout() may be stuck.
1539        final int numItems = 19;
1540        final int[] itemsLength = new int[numItems];
1541        for (int i = 0; i < numItems; i++) {
1542            itemsLength[i] = 288;
1543        }
1544        Intent intent = new Intent();
1545        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1546                R.layout.horizontal_linear);
1547        intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
1548        initActivity(intent);
1549        mOrientation = BaseGridView.HORIZONTAL;
1550        mNumRows = 1;
1551
1552        // set left right padding to 112, space between items to be 16.
1553        mActivityTestRule.runOnUiThread(new Runnable() {
1554            @Override
1555            public void run() {
1556                ViewGroup.LayoutParams lp = mGridView.getLayoutParams();
1557                lp.width = 1920;
1558                mGridView.setLayoutParams(lp);
1559                mGridView.setPadding(112, mGridView.getPaddingTop(), 112,
1560                        mGridView.getPaddingBottom());
1561                mGridView.setItemSpacing(16);
1562            }
1563        });
1564        waitOneUiCycle();
1565
1566        int scrollPos = 0;
1567        while (true) {
1568            final View view = mGridView.getChildAt(mGridView.getChildCount() - 1);
1569            final int pos = mGridView.getChildViewHolder(view).getAdapterPosition();
1570            if (scrollPos != pos) {
1571                scrollPos = pos;
1572                mActivityTestRule.runOnUiThread(new Runnable() {
1573                    @Override
1574                    public void run() {
1575                        mGridView.smoothScrollToPosition(pos);
1576                    }
1577                });
1578            }
1579            // wait until we see 2nd from last:
1580            if (pos >= 17) {
1581                if (pos == 17) {
1582                    // great we can test fastRelayout() bug.
1583                    Thread.sleep(50);
1584                    mActivityTestRule.runOnUiThread(new Runnable() {
1585                        @Override
1586                        public void run() {
1587                            view.requestLayout();
1588                        }
1589                    });
1590                }
1591                break;
1592            }
1593            Thread.sleep(16);
1594        }
1595        waitForScrollIdle();
1596    }
1597
1598    @Test
1599    public void testSwapAfterScroll() throws Throwable {
1600        Intent intent = new Intent();
1601        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1602                R.layout.horizontal_linear);
1603        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1604        initActivity(intent);
1605        mOrientation = BaseGridView.HORIZONTAL;
1606        mNumRows = 1;
1607
1608        mActivityTestRule.runOnUiThread(new Runnable() {
1609            @Override
1610            public void run() {
1611                mGridView.getItemAnimator().setMoveDuration(1000);
1612                mGridView.setSelectedPositionSmooth(150);
1613            }
1614        });
1615        waitForScrollIdle();
1616        mActivityTestRule.runOnUiThread(new Runnable() {
1617            @Override
1618            public void run() {
1619                mGridView.setSelectedPositionSmooth(151);
1620            }
1621        });
1622        mActivityTestRule.runOnUiThread(new Runnable() {
1623            @Override
1624            public void run() {
1625                // we want to swap and select new target which is at 150 before swap
1626                mGridView.setSelectedPositionSmooth(150);
1627                mActivity.swap(150, 151);
1628            }
1629        });
1630        waitForItemAnimation();
1631        waitForScrollIdle();
1632        assertEquals(151, mGridView.getSelectedPosition());
1633        // check if ItemAnimation finishes at aligned positions:
1634        int leftEdge = mGridView.getLayoutManager().findViewByPosition(151).getLeft();
1635        mActivityTestRule.runOnUiThread(new Runnable() {
1636            @Override
1637            public void run() {
1638                mGridView.requestLayout();
1639            }
1640        });
1641        waitForScrollIdle();
1642        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(151).getLeft());
1643    }
1644
1645    void testScrollInSmoothScrolling(final boolean smooth, final boolean scrollToInvisible,
1646            final boolean useRecyclerViewMethod) throws Throwable {
1647        final int numItems = 100;
1648        final int[] itemsLength = new int[numItems];
1649        for (int i = 0; i < numItems; i++) {
1650            itemsLength[i] = 288;
1651        }
1652        Intent intent = new Intent();
1653        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1654                R.layout.horizontal_linear);
1655        intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
1656        initActivity(intent);
1657        mOrientation = BaseGridView.HORIZONTAL;
1658        mNumRows = 1;
1659
1660        // start a smoothScroller
1661        final int selectedPosition = 99;
1662        mActivityTestRule.runOnUiThread(new Runnable() {
1663            @Override
1664            public void run() {
1665                mGridView.smoothScrollToPosition(selectedPosition);
1666            }
1667        });
1668        Thread.sleep(50);
1669        // while smoothScroller is still running, scroll to a different position
1670        final int[] existing_position = new int[1];
1671        mActivityTestRule.runOnUiThread(new Runnable() {
1672            @Override
1673            public void run() {
1674                existing_position[0] = mGridView.getChildAdapterPosition(
1675                        mGridView.getChildAt(mGridView.getChildCount() - 1));
1676                if (scrollToInvisible) {
1677                    existing_position[0] = existing_position[0] + 3;
1678                }
1679                if (useRecyclerViewMethod) {
1680                    if (smooth) {
1681                        mGridView.smoothScrollToPosition(existing_position[0]);
1682                    } else {
1683                        mGridView.scrollToPosition(existing_position[0]);
1684                    }
1685                } else {
1686                    if (smooth) {
1687                        mGridView.setSelectedPositionSmooth(existing_position[0]);
1688                    } else {
1689                        mGridView.setSelectedPosition(existing_position[0]);
1690                    }
1691                }
1692            }
1693        });
1694        waitForScrollIdle();
1695        assertEquals(existing_position[0], mGridView.getSelectedPosition());
1696        assertTrue(mGridView.findViewHolderForAdapterPosition(existing_position[0])
1697                .itemView.hasFocus());
1698    }
1699
1700    @Test
1701    public void testScrollInSmoothScrolling1() throws Throwable {
1702        testScrollInSmoothScrolling(false, false, false);
1703    }
1704
1705    @Test
1706    public void testScrollInSmoothScrolling2() throws Throwable {
1707        testScrollInSmoothScrolling(false, false, true);
1708    }
1709
1710    @Test
1711    public void testScrollInSmoothScrolling3() throws Throwable {
1712        testScrollInSmoothScrolling(false, true, false);
1713    }
1714
1715    @Test
1716    public void testScrollInSmoothScrolling4() throws Throwable {
1717        testScrollInSmoothScrolling(false, true, true);
1718    }
1719
1720    @Test
1721    public void testScrollInSmoothScrolling5() throws Throwable {
1722        testScrollInSmoothScrolling(true, false, false);
1723    }
1724
1725    @Test
1726    public void testScrollInSmoothScrolling6() throws Throwable {
1727        testScrollInSmoothScrolling(true, false, true);
1728    }
1729
1730    @Test
1731    public void testScrollInSmoothScrolling7() throws Throwable {
1732        testScrollInSmoothScrolling(true, true, false);
1733    }
1734
1735    @Test
1736    public void testScrollInSmoothScrolling8() throws Throwable {
1737        testScrollInSmoothScrolling(true, true, true);
1738    }
1739
1740    @Test
1741    public void testScrollAfterRequestLayout() throws Throwable {
1742        Intent intent = new Intent();
1743        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1744                R.layout.horizontal_linear);
1745        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
1746        initActivity(intent);
1747        mOrientation = BaseGridView.HORIZONTAL;
1748        mNumRows = 1;
1749        mActivityTestRule.runOnUiThread(new Runnable() {
1750            @Override
1751            public void run() {
1752                mGridView.setHasFixedSize(false);
1753                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
1754                mGridView.setWindowAlignmentOffsetPercent(30);
1755            }
1756        });
1757        waitOneUiCycle();
1758
1759        final boolean[] scrolled = new boolean[1];
1760        mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
1761            @Override
1762            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
1763                if (dx != 0)  scrolled[0] = true;
1764            }
1765        });
1766        mActivityTestRule.runOnUiThread(new Runnable() {
1767            @Override
1768            public void run() {
1769                mGridView.requestLayout();
1770                mGridView.setSelectedPosition(1);
1771            }
1772        });
1773        waitOneUiCycle();
1774        assertFalse(scrolled[0]);
1775    }
1776
1777    @Test
1778    public void testScrollAfterItemAnimator() throws Throwable {
1779        Intent intent = new Intent();
1780        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1781                R.layout.horizontal_linear);
1782        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
1783        initActivity(intent);
1784        mOrientation = BaseGridView.HORIZONTAL;
1785        mNumRows = 1;
1786        mActivityTestRule.runOnUiThread(new Runnable() {
1787            @Override
1788            public void run() {
1789                mGridView.setHasFixedSize(false);
1790                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
1791                mGridView.setWindowAlignmentOffsetPercent(30);
1792            }
1793        });
1794        waitOneUiCycle();
1795
1796        final boolean[] scrolled = new boolean[1];
1797        mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
1798            @Override
1799            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
1800                if (dx != 0)  scrolled[0] = true;
1801            }
1802        });
1803        mActivityTestRule.runOnUiThread(new Runnable() {
1804            @Override
1805            public void run() {
1806                mActivity.changeItem(0, 10);
1807                mGridView.setSelectedPosition(1);
1808            }
1809        });
1810        waitOneUiCycle();
1811        assertFalse(scrolled[0]);
1812    }
1813
1814    @Test
1815    public void testItemMovedHorizontal() throws Throwable {
1816        Intent intent = new Intent();
1817        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1818                R.layout.horizontal_grid);
1819        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1820        initActivity(intent);
1821        mOrientation = BaseGridView.HORIZONTAL;
1822        mNumRows = 3;
1823
1824        mActivityTestRule.runOnUiThread(new Runnable() {
1825            @Override
1826            public void run() {
1827                mGridView.setSelectedPositionSmooth(150);
1828            }
1829        });
1830        waitForScrollIdle(mVerifyLayout);
1831        performAndWaitForAnimation(new Runnable() {
1832            @Override
1833            public void run() {
1834                mActivity.swap(150, 152);
1835            }
1836        });
1837        mActivityTestRule.runOnUiThread(mVerifyLayout);
1838
1839        scrollToBegin(mVerifyLayout);
1840
1841        verifyBeginAligned();
1842    }
1843
1844    @Test
1845    public void testItemMovedHorizontalRtl() throws Throwable {
1846        Intent intent = new Intent();
1847        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1848                R.layout.horizontal_linear_rtl);
1849        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1850        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {40, 40, 40});
1851        initActivity(intent);
1852        mOrientation = BaseGridView.HORIZONTAL;
1853        mNumRows = 1;
1854
1855        performAndWaitForAnimation(new Runnable() {
1856            @Override
1857            public void run() {
1858                mActivity.moveItem(0, 1, true);
1859            }
1860        });
1861        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
1862                mGridView.findViewHolderForAdapterPosition(0).itemView.getRight());
1863    }
1864
1865    @Test
1866    public void testScrollSecondaryCannotScroll() throws Throwable {
1867        Intent intent = new Intent();
1868        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1869                R.layout.horizontal_grid);
1870        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1871        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
1872        initActivity(intent);
1873        mOrientation = BaseGridView.HORIZONTAL;
1874        mNumRows = 3;
1875        final int topPadding = 2;
1876        final int bottomPadding = 2;
1877        final int height = mGridView.getHeight();
1878        final int spacing = 2;
1879        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
1880        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
1881
1882        startWaitLayout();
1883        mActivityTestRule.runOnUiThread(new Runnable() {
1884            @Override
1885            public void run() {
1886                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
1887                horizontalGridView.setItemSpacing(spacing);
1888                horizontalGridView.setNumRows(mNumRows);
1889                horizontalGridView.setRowHeight(rowHeight);
1890            }
1891        });
1892        waitForLayout();
1893        // navigate vertically in first column, first row should always be aligned to top padding
1894        for (int i = 0; i < 3; i++) {
1895            setSelectedPosition(i);
1896            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView
1897                    .getTop());
1898        }
1899        // navigate vertically in 100th column, first row should always be aligned to top padding
1900        for (int i = 300; i < 301; i++) {
1901            setSelectedPosition(i);
1902            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(300).itemView
1903                    .getTop());
1904        }
1905    }
1906
1907    @Test
1908    public void testScrollSecondaryNeedScroll() throws Throwable {
1909        Intent intent = new Intent();
1910        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1911                R.layout.horizontal_grid);
1912        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1913        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
1914        initActivity(intent);
1915        mOrientation = BaseGridView.HORIZONTAL;
1916        // test a lot of rows so we have to scroll vertically to reach
1917        mNumRows = 9;
1918        final int topPadding = 2;
1919        final int bottomPadding = 2;
1920        final int height = mGridView.getHeight();
1921        final int spacing = 2;
1922        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
1923        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
1924
1925        startWaitLayout();
1926        mActivityTestRule.runOnUiThread(new Runnable() {
1927            @Override
1928            public void run() {
1929                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
1930                horizontalGridView.setItemSpacing(spacing);
1931                horizontalGridView.setNumRows(mNumRows);
1932                horizontalGridView.setRowHeight(rowHeight);
1933            }
1934        });
1935        waitForLayout();
1936        View view;
1937        // first row should be aligned to top padding
1938        setSelectedPosition(0);
1939        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
1940        // middle row should be aligned to keyline (1/2 of screen height)
1941        setSelectedPosition(4);
1942        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
1943        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
1944        // last row should be aligned to bottom padding.
1945        setSelectedPosition(8);
1946        view = mGridView.findViewHolderForAdapterPosition(8).itemView;
1947        assertEquals(height, view.getTop() + rowHeight + bottomPadding);
1948        setSelectedPositionSmooth(4);
1949        waitForScrollIdle();
1950        // middle row should be aligned to keyline (1/2 of screen height)
1951        setSelectedPosition(4);
1952        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
1953        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
1954        // first row should be aligned to top padding
1955        setSelectedPositionSmooth(0);
1956        waitForScrollIdle();
1957        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
1958    }
1959
1960    @Test
1961    public void testItemMovedVertical() throws Throwable {
1962
1963        Intent intent = new Intent();
1964        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1965                R.layout.vertical_grid);
1966        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1967        initActivity(intent);
1968        mOrientation = BaseGridView.VERTICAL;
1969        mNumRows = 3;
1970
1971        mGridView.setSelectedPositionSmooth(150);
1972        waitForScrollIdle(mVerifyLayout);
1973        performAndWaitForAnimation(new Runnable() {
1974            @Override
1975            public void run() {
1976                mActivity.swap(150, 152);
1977            }
1978        });
1979        mActivityTestRule.runOnUiThread(mVerifyLayout);
1980
1981        scrollToEnd(mVerifyLayout);
1982        scrollToBegin(mVerifyLayout);
1983
1984        verifyBeginAligned();
1985    }
1986
1987    @Test
1988    public void testAddLastItemHorizontal() throws Throwable {
1989
1990        Intent intent = new Intent();
1991        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1992                R.layout.horizontal_linear);
1993        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
1994        initActivity(intent);
1995        mOrientation = BaseGridView.HORIZONTAL;
1996        mNumRows = 1;
1997
1998        mActivityTestRule.runOnUiThread(
1999                new Runnable() {
2000                    @Override
2001                    public void run() {
2002                        mGridView.setSelectedPositionSmooth(49);
2003                    }
2004                }
2005        );
2006        waitForScrollIdle(mVerifyLayout);
2007        performAndWaitForAnimation(new Runnable() {
2008            @Override
2009            public void run() {
2010                mActivity.addItems(50, new int[]{150});
2011            }
2012        });
2013
2014        // assert new added item aligned to right edge
2015        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
2016                mGridView.getLayoutManager().findViewByPosition(50).getRight());
2017    }
2018
2019    @Test
2020    public void testAddMultipleLastItemsHorizontal() throws Throwable {
2021
2022        Intent intent = new Intent();
2023        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2024                R.layout.horizontal_linear);
2025        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
2026        initActivity(intent);
2027        mOrientation = BaseGridView.HORIZONTAL;
2028        mNumRows = 1;
2029
2030        mActivityTestRule.runOnUiThread(
2031                new Runnable() {
2032                    @Override
2033                    public void run() {
2034                        mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
2035                        mGridView.setWindowAlignmentOffsetPercent(50);
2036                        mGridView.setSelectedPositionSmooth(49);
2037                    }
2038                }
2039        );
2040        waitForScrollIdle(mVerifyLayout);
2041        performAndWaitForAnimation(new Runnable() {
2042            @Override
2043            public void run() {
2044                mActivity.addItems(50, new int[]{150, 150, 150, 150, 150, 150, 150, 150, 150,
2045                        150, 150, 150, 150, 150});
2046            }
2047        });
2048
2049        // The focused item will be at center of window
2050        View view = mGridView.getLayoutManager().findViewByPosition(49);
2051        assertEquals(mGridView.getWidth() / 2, (view.getLeft() + view.getRight()) / 2);
2052    }
2053
2054    @Test
2055    public void testItemAddRemoveHorizontal() throws Throwable {
2056
2057        Intent intent = new Intent();
2058        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2059                R.layout.horizontal_grid);
2060        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
2061        initActivity(intent);
2062        mOrientation = BaseGridView.HORIZONTAL;
2063        mNumRows = 3;
2064
2065        scrollToEnd(mVerifyLayout);
2066        int[] endEdges = getEndEdges();
2067
2068        mGridView.setSelectedPositionSmooth(150);
2069        waitForScrollIdle(mVerifyLayout);
2070        performAndWaitForAnimation(new Runnable() {
2071            @Override
2072            public void run() {
2073                mRemovedItems = mActivity.removeItems(151, 4);
2074            }
2075        });
2076
2077        scrollToEnd(mVerifyLayout);
2078        mGridView.setSelectedPositionSmooth(150);
2079        waitForScrollIdle(mVerifyLayout);
2080
2081        performAndWaitForAnimation(new Runnable() {
2082            @Override
2083            public void run() {
2084                mActivity.addItems(151, mRemovedItems);
2085            }
2086        });
2087        scrollToEnd(mVerifyLayout);
2088
2089        // we should get same aligned end edges
2090        int[] endEdges2 = getEndEdges();
2091        verifyEdgesSame(endEdges, endEdges2);
2092
2093        scrollToBegin(mVerifyLayout);
2094        verifyBeginAligned();
2095    }
2096
2097    @Test
2098    public void testSetSelectedPositionDetached() throws Throwable {
2099
2100        Intent intent = new Intent();
2101        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2102                R.layout.horizontal_linear);
2103        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
2104        initActivity(intent);
2105        mOrientation = BaseGridView.HORIZONTAL;
2106        mNumRows = 1;
2107
2108        final int focusToIndex = 49;
2109        final ViewGroup parent = (ViewGroup) mGridView.getParent();
2110        mActivityTestRule.runOnUiThread(new Runnable() {
2111            @Override
2112            public void run() {
2113                parent.removeView(mGridView);
2114            }
2115        });
2116        mActivityTestRule.runOnUiThread(new Runnable() {
2117            @Override
2118            public void run() {
2119                mGridView.setSelectedPositionSmooth(focusToIndex);
2120            }
2121        });
2122        mActivityTestRule.runOnUiThread(new Runnable() {
2123            @Override
2124            public void run() {
2125                parent.addView(mGridView);
2126                mGridView.requestFocus();
2127            }
2128        });
2129        waitForScrollIdle();
2130        assertEquals(mGridView.getSelectedPosition(), focusToIndex);
2131        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus());
2132
2133        final int focusToIndex2 = 0;
2134        mActivityTestRule.runOnUiThread(new Runnable() {
2135            @Override
2136            public void run() {
2137                parent.removeView(mGridView);
2138            }
2139        });
2140        mActivityTestRule.runOnUiThread(new Runnable() {
2141            @Override
2142            public void run() {
2143                mGridView.setSelectedPosition(focusToIndex2);
2144            }
2145        });
2146        mActivityTestRule.runOnUiThread(new Runnable() {
2147            @Override
2148            public void run() {
2149                parent.addView(mGridView);
2150                mGridView.requestFocus();
2151            }
2152        });
2153        assertEquals(mGridView.getSelectedPosition(), focusToIndex2);
2154        waitForScrollIdle();
2155        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus());
2156    }
2157
2158    @Test
2159    public void testBug22209986() throws Throwable {
2160
2161        Intent intent = new Intent();
2162        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2163                R.layout.horizontal_linear);
2164        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
2165        initActivity(intent);
2166        mOrientation = BaseGridView.HORIZONTAL;
2167        mNumRows = 1;
2168
2169        final int focusToIndex = mGridView.getChildCount() - 1;
2170        mActivityTestRule.runOnUiThread(new Runnable() {
2171            @Override
2172            public void run() {
2173                mGridView.setSelectedPositionSmooth(focusToIndex);
2174            }
2175        });
2176
2177        waitForScrollIdle();
2178        mActivityTestRule.runOnUiThread(new Runnable() {
2179            @Override
2180            public void run() {
2181                mGridView.setSelectedPositionSmooth(focusToIndex + 1);
2182            }
2183        });
2184        // let the scroll running for a while and requestLayout during scroll
2185        Thread.sleep(80);
2186        mActivityTestRule.runOnUiThread(new Runnable() {
2187            @Override
2188            public void run() {
2189                assertEquals(mGridView.getScrollState(), BaseGridView.SCROLL_STATE_SETTLING);
2190                mGridView.requestLayout();
2191            }
2192        });
2193        waitForScrollIdle();
2194
2195        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
2196
2197        mActivityTestRule.runOnUiThread(new Runnable() {
2198            @Override
2199            public void run() {
2200                mGridView.requestLayout();
2201            }
2202        });
2203        waitForScrollIdle();
2204        assertEquals(leftEdge,
2205                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
2206    }
2207
2208    void testScrollAndRemove(int[] itemsLength, int numItems) throws Throwable {
2209
2210        Intent intent = new Intent();
2211        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2212                R.layout.horizontal_linear);
2213        if (itemsLength != null) {
2214            intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
2215        } else {
2216            intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2217        }
2218        initActivity(intent);
2219        mOrientation = BaseGridView.HORIZONTAL;
2220        mNumRows = 1;
2221
2222        final int focusToIndex = mGridView.getChildCount() - 1;
2223        mActivityTestRule.runOnUiThread(new Runnable() {
2224            @Override
2225            public void run() {
2226                mGridView.setSelectedPositionSmooth(focusToIndex);
2227            }
2228        });
2229
2230        performAndWaitForAnimation(new Runnable() {
2231            @Override
2232            public void run() {
2233                mActivity.removeItems(focusToIndex, 1);
2234            }
2235        });
2236
2237        waitOneUiCycle();
2238        waitForScrollIdle();
2239        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
2240
2241        mActivityTestRule.runOnUiThread(new Runnable() {
2242            @Override
2243            public void run() {
2244                mGridView.requestLayout();
2245            }
2246        });
2247        waitForScrollIdle();
2248        assertEquals(leftEdge,
2249                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(), DELTA);
2250    }
2251
2252    @Test
2253    public void testScrollAndRemove() throws Throwable {
2254        // test random lengths for 50 items
2255        testScrollAndRemove(null, 50);
2256    }
2257
2258    /**
2259     * This test verifies if scroll limits are ignored when onLayoutChildren compensate remaining
2260     * scroll distance. b/64931938
2261     * In the test, second child is long, other children are short.
2262     * Test scrolls to the long child, and when scrolling, remove the long child. We made it long
2263     * to have enough remaining scroll distance when the layout pass kicks in.
2264     * The onLayoutChildren() would compensate the remaining scroll distance, moving all items
2265     * toward right, which will make the first item's left edge bigger than left padding,
2266     * which would violate the "scroll limit of left" in a regular scroll case, but
2267     * in layout pass, we still honor that scroll request, ignoring the scroll limit.
2268     */
2269    @Test
2270    public void testScrollAndRemoveSample1() throws Throwable {
2271        DisplayMetrics dm = InstrumentationRegistry.getInstrumentation().getTargetContext()
2272                .getResources().getDisplayMetrics();
2273        // screen width for long item and 4DP for other items
2274        int longItemLength = dm.widthPixels;
2275        int shortItemLength = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, dm);
2276        int[] items = new int[1000];
2277        for (int i = 0; i < items.length; i++) {
2278            items[i] = shortItemLength;
2279        }
2280        items[1] = longItemLength;
2281        testScrollAndRemove(items, 0);
2282    }
2283
2284    @Test
2285    public void testScrollAndInsert() throws Throwable {
2286
2287        Intent intent = new Intent();
2288        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2289                R.layout.vertical_grid);
2290        int[] items = new int[1000];
2291        for (int i = 0; i < items.length; i++) {
2292            items[i] = 300 + (int)(Math.random() * 100);
2293        }
2294        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2295        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
2296        mOrientation = BaseGridView.VERTICAL;
2297        mNumRows = 3;
2298
2299        initActivity(intent);
2300
2301        mActivityTestRule.runOnUiThread(new Runnable() {
2302            @Override
2303            public void run() {
2304                mGridView.setSelectedPositionSmooth(150);
2305            }
2306        });
2307        waitForScrollIdle(mVerifyLayout);
2308
2309        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
2310        final int focusToIndex = mGridView.getChildAdapterPosition(view);
2311        mActivityTestRule.runOnUiThread(new Runnable() {
2312            @Override
2313            public void run() {
2314                mGridView.setSelectedPositionSmooth(focusToIndex);
2315            }
2316        });
2317
2318        mActivityTestRule.runOnUiThread(new Runnable() {
2319            @Override
2320            public void run() {
2321                int[] newItems = new int[]{300, 300, 300};
2322                mActivity.addItems(0, newItems);
2323            }
2324        });
2325        waitForScrollIdle();
2326        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop();
2327        mActivityTestRule.runOnUiThread(new Runnable() {
2328            @Override
2329            public void run() {
2330                mGridView.requestLayout();
2331            }
2332        });
2333        waitForScrollIdle();
2334        assertEquals(topEdge,
2335                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop());
2336    }
2337
2338    @Test
2339    public void testScrollAndInsertBeforeVisibleItem() throws Throwable {
2340
2341        Intent intent = new Intent();
2342        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2343                R.layout.vertical_grid);
2344        int[] items = new int[1000];
2345        for (int i = 0; i < items.length; i++) {
2346            items[i] = 300 + (int)(Math.random() * 100);
2347        }
2348        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2349        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
2350        mOrientation = BaseGridView.VERTICAL;
2351        mNumRows = 3;
2352
2353        initActivity(intent);
2354
2355        mActivityTestRule.runOnUiThread(new Runnable() {
2356            @Override
2357            public void run() {
2358                mGridView.setSelectedPositionSmooth(150);
2359            }
2360        });
2361        waitForScrollIdle(mVerifyLayout);
2362
2363        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
2364        final int focusToIndex = mGridView.getChildAdapterPosition(view);
2365        mActivityTestRule.runOnUiThread(new Runnable() {
2366            @Override
2367            public void run() {
2368                mGridView.setSelectedPositionSmooth(focusToIndex);
2369            }
2370        });
2371
2372        performAndWaitForAnimation(new Runnable() {
2373            @Override
2374            public void run() {
2375                int[] newItems = new int[]{300, 300, 300};
2376                mActivity.addItems(focusToIndex, newItems);
2377            }
2378        });
2379    }
2380
2381    @Test
2382    public void testSmoothScrollAndRemove() throws Throwable {
2383
2384        Intent intent = new Intent();
2385        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2386                R.layout.horizontal_linear);
2387        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
2388        initActivity(intent);
2389        mOrientation = BaseGridView.HORIZONTAL;
2390        mNumRows = 1;
2391
2392        final int focusToIndex = 200;
2393        mActivityTestRule.runOnUiThread(new Runnable() {
2394            @Override
2395            public void run() {
2396                mGridView.setSelectedPositionSmooth(focusToIndex);
2397            }
2398        });
2399
2400        mActivityTestRule.runOnUiThread(new Runnable() {
2401            @Override
2402            public void run() {
2403                mActivity.removeItems(focusToIndex, 1);
2404            }
2405        });
2406
2407        assertTrue("removing the index of not attached child should not affect smooth scroller",
2408                mGridView.getLayoutManager().isSmoothScrolling());
2409        waitForScrollIdle();
2410        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
2411
2412        mActivityTestRule.runOnUiThread(new Runnable() {
2413            @Override
2414            public void run() {
2415                mGridView.requestLayout();
2416            }
2417        });
2418        waitForScrollIdle();
2419        assertEquals(leftEdge,
2420                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
2421    }
2422
2423    @Test
2424    public void testSmoothScrollAndRemove2() throws Throwable {
2425
2426        Intent intent = new Intent();
2427        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2428                R.layout.horizontal_linear);
2429        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
2430        initActivity(intent);
2431        mOrientation = BaseGridView.HORIZONTAL;
2432        mNumRows = 1;
2433
2434        final int focusToIndex = 200;
2435        mActivityTestRule.runOnUiThread(new Runnable() {
2436            @Override
2437            public void run() {
2438                mGridView.setSelectedPositionSmooth(focusToIndex);
2439            }
2440        });
2441
2442        startWaitLayout();
2443        mActivityTestRule.runOnUiThread(new Runnable() {
2444            @Override
2445            public void run() {
2446                final int removeIndex = mGridView.getChildViewHolder(
2447                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
2448                mActivity.removeItems(removeIndex, 1);
2449            }
2450        });
2451        waitForLayout();
2452
2453        assertTrue("removing the index of attached child should not kill smooth scroller",
2454                mGridView.getLayoutManager().isSmoothScrolling());
2455        waitForItemAnimation();
2456        waitForScrollIdle();
2457        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
2458
2459        mActivityTestRule.runOnUiThread(new Runnable() {
2460            @Override
2461            public void run() {
2462                mGridView.requestLayout();
2463            }
2464        });
2465        waitForScrollIdle();
2466        assertEquals(leftEdge,
2467                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
2468    }
2469
2470    @Test
2471    public void testPendingSmoothScrollAndRemove() throws Throwable {
2472        Intent intent = new Intent();
2473        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2474                R.layout.vertical_linear);
2475        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
2476        int[] items = new int[100];
2477        for (int i = 0; i < items.length; i++) {
2478            items[i] = 630 + (int)(Math.random() * 100);
2479        }
2480        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2481        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
2482        mOrientation = BaseGridView.VERTICAL;
2483        mNumRows = 1;
2484
2485        initActivity(intent);
2486
2487        mGridView.setSelectedPositionSmooth(0);
2488        waitForScrollIdle(mVerifyLayout);
2489        assertTrue(mGridView.getChildAt(0).hasFocus());
2490
2491        // Pressing lots of key to make sure smooth scroller is running
2492        mGridView.mLayoutManager.mMaxPendingMoves = 100;
2493        for (int i = 0; i < 100; i++) {
2494            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
2495        }
2496
2497        assertTrue(mGridView.getLayoutManager().isSmoothScrolling());
2498        startWaitLayout();
2499        mActivityTestRule.runOnUiThread(new Runnable() {
2500            @Override
2501            public void run() {
2502                final int removeIndex = mGridView.getChildViewHolder(
2503                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
2504                mActivity.removeItems(removeIndex, 1);
2505            }
2506        });
2507        waitForLayout();
2508
2509        assertTrue("removing the index of attached child should not kill smooth scroller",
2510                mGridView.getLayoutManager().isSmoothScrolling());
2511
2512        waitForItemAnimation();
2513        waitForScrollIdle();
2514        int focusIndex = mGridView.getSelectedPosition();
2515        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
2516
2517        mActivityTestRule.runOnUiThread(new Runnable() {
2518            @Override
2519            public void run() {
2520                mGridView.requestLayout();
2521            }
2522        });
2523        waitForScrollIdle();
2524        assertEquals(topEdge,
2525                mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop());
2526    }
2527
2528    @Test
2529    public void testFocusToFirstItem() throws Throwable {
2530
2531        Intent intent = new Intent();
2532        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2533                R.layout.horizontal_grid);
2534        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
2535        initActivity(intent);
2536        mOrientation = BaseGridView.HORIZONTAL;
2537        mNumRows = 3;
2538
2539        performAndWaitForAnimation(new Runnable() {
2540            @Override
2541            public void run() {
2542                mRemovedItems = mActivity.removeItems(0, 200);
2543            }
2544        });
2545
2546        humanDelay(500);
2547        performAndWaitForAnimation(new Runnable() {
2548            @Override
2549            public void run() {
2550                mActivity.addItems(0, mRemovedItems);
2551            }
2552        });
2553
2554        humanDelay(500);
2555        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
2556
2557        changeArraySize(0);
2558
2559        changeArraySize(200);
2560        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
2561    }
2562
2563    @Test
2564    public void testNonFocusableHorizontal() throws Throwable {
2565        final int numItems = 200;
2566        final int startPos = 45;
2567        final int skips = 20;
2568        final int numColumns = 3;
2569        final int endPos = startPos + numColumns * (skips + 1);
2570
2571        Intent intent = new Intent();
2572        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2573                R.layout.horizontal_grid);
2574        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2575        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2576        mOrientation = BaseGridView.HORIZONTAL;
2577        mNumRows = numColumns;
2578        boolean[] focusable = new boolean[numItems];
2579        for (int i = 0; i < focusable.length; i++) {
2580            focusable[i] = true;
2581        }
2582        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
2583            focusable[i] = false;
2584        }
2585        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2586        initActivity(intent);
2587
2588        mGridView.setSelectedPositionSmooth(startPos);
2589        waitForScrollIdle(mVerifyLayout);
2590
2591        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
2592            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
2593        } else {
2594            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
2595        }
2596        waitForScrollIdle(mVerifyLayout);
2597        assertEquals(endPos, mGridView.getSelectedPosition());
2598
2599        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
2600            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
2601        } else {
2602            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
2603        }
2604        waitForScrollIdle(mVerifyLayout);
2605        assertEquals(startPos, mGridView.getSelectedPosition());
2606
2607    }
2608
2609    @Test
2610    public void testNoInitialFocusable() throws Throwable {
2611
2612        Intent intent = new Intent();
2613        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2614                R.layout.horizontal_linear);
2615        final int numItems = 100;
2616        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2617        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2618        mOrientation = BaseGridView.HORIZONTAL;
2619        mNumRows = 1;
2620        boolean[] focusable = new boolean[numItems];
2621        final int firstFocusableIndex = 10;
2622        for (int i = 0; i < firstFocusableIndex; i++) {
2623            focusable[i] = false;
2624        }
2625        for (int i = firstFocusableIndex; i < focusable.length; i++) {
2626            focusable[i] = true;
2627        }
2628        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2629        initActivity(intent);
2630        assertTrue(mGridView.isFocused());
2631
2632        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
2633            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
2634        } else {
2635            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
2636        }
2637        waitForScrollIdle(mVerifyLayout);
2638        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
2639        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
2640    }
2641
2642    @Test
2643    public void testFocusOutOfEmptyListView() throws Throwable {
2644
2645        Intent intent = new Intent();
2646        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2647                R.layout.horizontal_linear);
2648        final int numItems = 100;
2649        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2650        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2651        mOrientation = BaseGridView.HORIZONTAL;
2652        mNumRows = 1;
2653        initActivity(intent);
2654
2655        final View horizontalGridView = new HorizontalGridViewEx(mGridView.getContext());
2656        mActivityTestRule.runOnUiThread(new Runnable() {
2657            @Override
2658            public void run() {
2659                horizontalGridView.setFocusable(true);
2660                horizontalGridView.setFocusableInTouchMode(true);
2661                horizontalGridView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
2662                ((ViewGroup) mGridView.getParent()).addView(horizontalGridView, 0);
2663                horizontalGridView.requestFocus();
2664            }
2665        });
2666
2667        assertTrue(horizontalGridView.isFocused());
2668
2669        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
2670
2671        assertTrue(mGridView.hasFocus());
2672    }
2673
2674    @Test
2675    public void testTransferFocusToChildWhenGainFocus() throws Throwable {
2676
2677        Intent intent = new Intent();
2678        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2679                R.layout.horizontal_linear);
2680        final int numItems = 100;
2681        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2682        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2683        mOrientation = BaseGridView.HORIZONTAL;
2684        mNumRows = 1;
2685        boolean[] focusable = new boolean[numItems];
2686        final int firstFocusableIndex = 1;
2687        for (int i = 0; i < firstFocusableIndex; i++) {
2688            focusable[i] = false;
2689        }
2690        for (int i = firstFocusableIndex; i < focusable.length; i++) {
2691            focusable[i] = true;
2692        }
2693        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2694        initActivity(intent);
2695
2696        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
2697        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
2698    }
2699
2700    @Test
2701    public void testFocusFromSecondChild() throws Throwable {
2702
2703        Intent intent = new Intent();
2704        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2705                R.layout.horizontal_linear);
2706        final int numItems = 100;
2707        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2708        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2709        mOrientation = BaseGridView.HORIZONTAL;
2710        mNumRows = 1;
2711        boolean[] focusable = new boolean[numItems];
2712        for (int i = 0; i < focusable.length; i++) {
2713            focusable[i] = false;
2714        }
2715        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2716        initActivity(intent);
2717
2718        // switching Adapter to cause a full rebind,  test if it will focus to second item.
2719        performAndWaitForAnimation(new Runnable() {
2720            @Override
2721            public void run() {
2722                mActivity.mNumItems = numItems;
2723                mActivity.mItemFocusables[1] = true;
2724                mActivity.rebindToNewAdapter();
2725            }
2726        });
2727        assertTrue(mGridView.findViewHolderForAdapterPosition(1).itemView.hasFocus());
2728    }
2729
2730    @Test
2731    public void removeFocusableItemAndFocusableRecyclerViewGetsFocus() throws Throwable {
2732        final int numItems = 100;
2733        final int numColumns = 3;
2734        final int focusableIndex = 2;
2735
2736        Intent intent = new Intent();
2737        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2738                R.layout.vertical_grid);
2739        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2740        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2741        mOrientation = BaseGridView.VERTICAL;
2742        mNumRows = numColumns;
2743        boolean[] focusable = new boolean[numItems];
2744        for (int i = 0; i < focusable.length; i++) {
2745            focusable[i] = false;
2746        }
2747        focusable[focusableIndex] = true;
2748        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2749        initActivity(intent);
2750
2751        mActivityTestRule.runOnUiThread(new Runnable() {
2752            @Override
2753            public void run() {
2754                mGridView.setSelectedPositionSmooth(focusableIndex);
2755            }
2756        });
2757        waitForScrollIdle(mVerifyLayout);
2758        assertEquals(focusableIndex, mGridView.getSelectedPosition());
2759
2760        performAndWaitForAnimation(new Runnable() {
2761            @Override
2762            public void run() {
2763                mActivity.removeItems(focusableIndex, 1);
2764            }
2765        });
2766        assertTrue(dumpGridView(mGridView), mGridView.isFocused());
2767    }
2768
2769    @Test
2770    public void removeFocusableItemAndUnFocusableRecyclerViewLosesFocus() throws Throwable {
2771        final int numItems = 100;
2772        final int numColumns = 3;
2773        final int focusableIndex = 2;
2774
2775        Intent intent = new Intent();
2776        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2777                R.layout.vertical_grid);
2778        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2779        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2780        mOrientation = BaseGridView.VERTICAL;
2781        mNumRows = numColumns;
2782        boolean[] focusable = new boolean[numItems];
2783        for (int i = 0; i < focusable.length; i++) {
2784            focusable[i] = false;
2785        }
2786        focusable[focusableIndex] = true;
2787        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2788        initActivity(intent);
2789
2790        mActivityTestRule.runOnUiThread(new Runnable() {
2791            @Override
2792            public void run() {
2793                mGridView.setFocusableInTouchMode(false);
2794                mGridView.setFocusable(false);
2795                mGridView.setSelectedPositionSmooth(focusableIndex);
2796            }
2797        });
2798        waitForScrollIdle(mVerifyLayout);
2799        assertEquals(focusableIndex, mGridView.getSelectedPosition());
2800
2801        performAndWaitForAnimation(new Runnable() {
2802            @Override
2803            public void run() {
2804                mActivity.removeItems(focusableIndex, 1);
2805            }
2806        });
2807        assertFalse(dumpGridView(mGridView), mGridView.hasFocus());
2808    }
2809
2810    @Test
2811    public void testNonFocusableVertical() throws Throwable {
2812        final int numItems = 200;
2813        final int startPos = 44;
2814        final int skips = 20;
2815        final int numColumns = 3;
2816        final int endPos = startPos + numColumns * (skips + 1);
2817
2818        Intent intent = new Intent();
2819        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2820                R.layout.vertical_grid);
2821        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2822        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2823        mOrientation = BaseGridView.VERTICAL;
2824        mNumRows = numColumns;
2825        boolean[] focusable = new boolean[numItems];
2826        for (int i = 0; i < focusable.length; i++) {
2827            focusable[i] = true;
2828        }
2829        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
2830            focusable[i] = false;
2831        }
2832        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2833        initActivity(intent);
2834
2835        mGridView.setSelectedPositionSmooth(startPos);
2836        waitForScrollIdle(mVerifyLayout);
2837
2838        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
2839        waitForScrollIdle(mVerifyLayout);
2840        assertEquals(endPos, mGridView.getSelectedPosition());
2841
2842        sendKey(KeyEvent.KEYCODE_DPAD_UP);
2843        waitForScrollIdle(mVerifyLayout);
2844        assertEquals(startPos, mGridView.getSelectedPosition());
2845
2846    }
2847
2848    @Test
2849    public void testLtrFocusOutStartDisabled() throws Throwable {
2850        final int numItems = 200;
2851
2852        Intent intent = new Intent();
2853        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr);
2854        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2855        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2856        mOrientation = BaseGridView.VERTICAL;
2857        mNumRows = 2;
2858        initActivity(intent);
2859
2860        mActivityTestRule.runOnUiThread(new Runnable() {
2861            @Override
2862            public void run() {
2863                mGridView.requestFocus();
2864                mGridView.setSelectedPositionSmooth(0);
2865            }
2866        });
2867        waitForScrollIdle(mVerifyLayout);
2868
2869        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
2870        waitForScrollIdle(mVerifyLayout);
2871        assertTrue(mGridView.hasFocus());
2872    }
2873
2874    @Test
2875    public void testVerticalGridRtl() throws Throwable {
2876        final int numItems = 200;
2877
2878        Intent intent = new Intent();
2879        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
2880        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2881        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2882        mOrientation = BaseGridView.VERTICAL;
2883        mNumRows = 2;
2884        initActivity(intent);
2885
2886        waitForScrollIdle(mVerifyLayout);
2887
2888        View item0 = mGridView.findViewHolderForAdapterPosition(0).itemView;
2889        View item1 = mGridView.findViewHolderForAdapterPosition(1).itemView;
2890        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(), item0.getRight());
2891        assertEquals(item0.getLeft(), item1.getRight() + mGridView.getHorizontalSpacing());
2892    }
2893
2894    @Test
2895    public void testRtlFocusOutStartDisabled() throws Throwable {
2896        final int numItems = 200;
2897
2898        Intent intent = new Intent();
2899        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
2900        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2901        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2902        mOrientation = BaseGridView.VERTICAL;
2903        mNumRows = 1;
2904        initActivity(intent);
2905
2906        mActivityTestRule.runOnUiThread(new Runnable() {
2907            @Override
2908            public void run() {
2909                mGridView.requestFocus();
2910                mGridView.setSelectedPositionSmooth(0);
2911            }
2912        });
2913        waitForScrollIdle(mVerifyLayout);
2914
2915        sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
2916        waitForScrollIdle(mVerifyLayout);
2917        assertTrue(mGridView.hasFocus());
2918    }
2919
2920    @Test
2921    public void testTransferFocusable() throws Throwable {
2922        final int numItems = 200;
2923        final int numColumns = 3;
2924        final int startPos = 1;
2925
2926        Intent intent = new Intent();
2927        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2928                R.layout.horizontal_grid);
2929        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2930        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2931        mOrientation = BaseGridView.HORIZONTAL;
2932        mNumRows = numColumns;
2933        boolean[] focusable = new boolean[numItems];
2934        for (int i = 0; i < focusable.length; i++) {
2935            focusable[i] = true;
2936        }
2937        for (int i = 0; i < startPos; i++) {
2938            focusable[i] = false;
2939        }
2940        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2941        initActivity(intent);
2942
2943        changeArraySize(0);
2944        assertTrue(mGridView.isFocused());
2945
2946        changeArraySize(numItems);
2947        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
2948    }
2949
2950    @Test
2951    public void testTransferFocusable2() throws Throwable {
2952        final int numItems = 200;
2953        final int numColumns = 3;
2954        final int startPos = 3; // make sure view at startPos is in visible area.
2955
2956        Intent intent = new Intent();
2957        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2958                R.layout.horizontal_grid);
2959        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
2960        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
2961        mOrientation = BaseGridView.HORIZONTAL;
2962        mNumRows = numColumns;
2963        boolean[] focusable = new boolean[numItems];
2964        for (int i = 0; i < focusable.length; i++) {
2965            focusable[i] = true;
2966        }
2967        for (int i = 0; i < startPos; i++) {
2968            focusable[i] = false;
2969        }
2970        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
2971        initActivity(intent);
2972
2973        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
2974
2975        changeArraySize(0);
2976        assertTrue(mGridView.isFocused());
2977
2978        changeArraySize(numItems);
2979        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
2980    }
2981
2982    @Test
2983    public void testNonFocusableLoseInFastLayout() throws Throwable {
2984        Intent intent = new Intent();
2985        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2986                R.layout.vertical_linear);
2987        int[] items = new int[300];
2988        for (int i = 0; i < items.length; i++) {
2989            items[i] = 480;
2990        }
2991        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2992        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2993        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
2994        mOrientation = BaseGridView.VERTICAL;
2995        mNumRows = 1;
2996        int pressDown = 15;
2997
2998        initActivity(intent);
2999
3000        mGridView.setSelectedPositionSmooth(0);
3001        waitForScrollIdle(mVerifyLayout);
3002
3003        for (int i = 0; i < pressDown; i++) {
3004            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3005        }
3006        waitForScrollIdle(mVerifyLayout);
3007        assertFalse(mGridView.isFocused());
3008
3009    }
3010
3011    @Test
3012    public void testFocusableViewAvailable() throws Throwable {
3013        Intent intent = new Intent();
3014        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3015                R.layout.vertical_linear);
3016        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
3017        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3018        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE,
3019                new boolean[]{false, false, true, false, false});
3020        mOrientation = BaseGridView.VERTICAL;
3021        mNumRows = 1;
3022
3023        initActivity(intent);
3024
3025        mActivityTestRule.runOnUiThread(new Runnable() {
3026            @Override
3027            public void run() {
3028                // RecyclerView does not respect focusable and focusableInTouchMode flag, so
3029                // set flags in code.
3030                mGridView.setFocusableInTouchMode(false);
3031                mGridView.setFocusable(false);
3032            }
3033        });
3034
3035        assertFalse(mGridView.isFocused());
3036
3037        final boolean[] scrolled = new boolean[]{false};
3038        mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
3039            @Override
3040            public void onScrolled(RecyclerView recyclerView, int dx, int dy){
3041                if (dy > 0) {
3042                    scrolled[0] = true;
3043                }
3044            }
3045        });
3046        performAndWaitForAnimation(new Runnable() {
3047            @Override
3048            public void run() {
3049                mActivity.addItems(0, new int[]{200, 300, 500, 500, 200});
3050            }
3051        });
3052        waitForScrollIdle(mVerifyLayout);
3053
3054        assertFalse("GridView should not be scrolled", scrolled[0]);
3055        assertTrue(mGridView.getLayoutManager().findViewByPosition(2).hasFocus());
3056
3057    }
3058
3059    @Test
3060    public void testSetSelectionWithDelta() throws Throwable {
3061        Intent intent = new Intent();
3062        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3063                R.layout.vertical_linear);
3064        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
3065        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3066        mOrientation = BaseGridView.VERTICAL;
3067        mNumRows = 1;
3068
3069        initActivity(intent);
3070
3071        mActivityTestRule.runOnUiThread(new Runnable() {
3072            @Override
3073            public void run() {
3074                mGridView.setSelectedPositionSmooth(3);
3075            }
3076        });
3077        waitForScrollIdle(mVerifyLayout);
3078        int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
3079
3080        humanDelay(1000);
3081
3082        // scroll to position with delta
3083        setSelectedPosition(3, 100);
3084        int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
3085        assertEquals(top1 - 100, top2);
3086
3087        // scroll to same position without delta, it will be reset
3088        setSelectedPosition(3, 0);
3089        int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
3090        assertEquals(top1, top3);
3091
3092        // scroll invisible item after last visible item
3093        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3094                .mGrid.getLastVisibleIndex();
3095        setSelectedPosition(lastVisiblePos + 1, 100);
3096        int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop();
3097        assertEquals(top1 - 100, top4);
3098
3099        // scroll invisible item before first visible item
3100        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3101                .mGrid.getFirstVisibleIndex();
3102        setSelectedPosition(firstVisiblePos - 1, 100);
3103        int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop();
3104        assertEquals(top1 - 100, top5);
3105
3106        // scroll to invisible item that is far away.
3107        setSelectedPosition(50, 100);
3108        int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop();
3109        assertEquals(top1 - 100, top6);
3110
3111        // scroll to invisible item that is far away.
3112        mActivityTestRule.runOnUiThread(new Runnable() {
3113            @Override
3114            public void run() {
3115                mGridView.setSelectedPositionSmooth(100);
3116            }
3117        });
3118        waitForScrollIdle(mVerifyLayout);
3119        int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop();
3120        assertEquals(top1, top7);
3121
3122        // scroll to invisible item that is far away.
3123        setSelectedPosition(10, 50);
3124        int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop();
3125        assertEquals(top1 - 50, top8);
3126    }
3127
3128    @Test
3129    public void testSetSelectionWithDeltaInGrid() throws Throwable {
3130        Intent intent = new Intent();
3131        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3132                R.layout.vertical_grid);
3133        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
3134        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
3135        mOrientation = BaseGridView.VERTICAL;
3136        mNumRows = 3;
3137
3138        initActivity(intent);
3139
3140        mActivityTestRule.runOnUiThread(new Runnable() {
3141            @Override
3142            public void run() {
3143                mGridView.setSelectedPositionSmooth(10);
3144            }
3145        });
3146        waitForScrollIdle(mVerifyLayout);
3147        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
3148
3149        humanDelay(500);
3150
3151        // scroll to position with delta
3152        setSelectedPosition(20, 100);
3153        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
3154        assertEquals(top1 - 100, top2);
3155
3156        // scroll to same position without delta, it will be reset
3157        setSelectedPosition(20, 0);
3158        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
3159        assertEquals(top1, top3);
3160
3161        // scroll invisible item after last visible item
3162        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3163                .mGrid.getLastVisibleIndex();
3164        setSelectedPosition(lastVisiblePos + 1, 100);
3165        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
3166        verifyMargin();
3167        assertEquals(top1 - 100, top4);
3168
3169        // scroll invisible item before first visible item
3170        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3171                .mGrid.getFirstVisibleIndex();
3172        setSelectedPosition(firstVisiblePos - 1, 100);
3173        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
3174        assertEquals(top1 - 100, top5);
3175
3176        // scroll to invisible item that is far away.
3177        setSelectedPosition(100, 100);
3178        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
3179        assertEquals(top1 - 100, top6);
3180
3181        // scroll to invisible item that is far away.
3182        mActivityTestRule.runOnUiThread(new Runnable() {
3183            @Override
3184            public void run() {
3185                mGridView.setSelectedPositionSmooth(200);
3186            }
3187        });
3188        waitForScrollIdle(mVerifyLayout);
3189        Thread.sleep(500);
3190        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
3191        assertEquals(top1, top7);
3192
3193        // scroll to invisible item that is far away.
3194        setSelectedPosition(10, 50);
3195        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
3196        assertEquals(top1 - 50, top8);
3197    }
3198
3199
3200    @Test
3201    public void testSetSelectionWithDeltaInGrid1() throws Throwable {
3202        Intent intent = new Intent();
3203        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3204                R.layout.vertical_grid);
3205        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
3206                193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137,
3207                188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205,
3208                223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222,
3209                150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236,
3210                123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128,
3211                192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218,
3212                192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191,
3213                212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141,
3214                156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191,
3215                132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184,
3216                164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226,
3217                132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226,
3218                127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169,
3219                182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220,
3220                178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125,
3221                214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122,
3222                188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191,
3223                199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210,
3224                164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130,
3225                137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227,
3226                193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154,
3227                192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124,
3228                172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174,
3229                146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225,
3230                174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162,
3231                192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221,
3232                120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190,
3233                204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125,
3234                202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123,
3235                237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162,
3236                139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199,
3237                215,213,122,197
3238        });
3239        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
3240        mOrientation = BaseGridView.VERTICAL;
3241        mNumRows = 3;
3242
3243        initActivity(intent);
3244
3245        mActivityTestRule.runOnUiThread(new Runnable() {
3246            @Override
3247            public void run() {
3248                mGridView.setSelectedPositionSmooth(10);
3249            }
3250        });
3251        waitForScrollIdle(mVerifyLayout);
3252        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
3253
3254        humanDelay(500);
3255
3256        // scroll to position with delta
3257        setSelectedPosition(20, 100);
3258        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
3259        assertEquals(top1 - 100, top2);
3260
3261        // scroll to same position without delta, it will be reset
3262        setSelectedPosition(20, 0);
3263        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
3264        assertEquals(top1, top3);
3265
3266        // scroll invisible item after last visible item
3267        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3268                .mGrid.getLastVisibleIndex();
3269        setSelectedPosition(lastVisiblePos + 1, 100);
3270        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
3271        verifyMargin();
3272        assertEquals(top1 - 100, top4);
3273
3274        // scroll invisible item before first visible item
3275        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
3276                .mGrid.getFirstVisibleIndex();
3277        setSelectedPosition(firstVisiblePos - 1, 100);
3278        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
3279        assertEquals(top1 - 100, top5);
3280
3281        // scroll to invisible item that is far away.
3282        setSelectedPosition(100, 100);
3283        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
3284        assertEquals(top1 - 100, top6);
3285
3286        // scroll to invisible item that is far away.
3287        mActivityTestRule.runOnUiThread(new Runnable() {
3288            @Override
3289            public void run() {
3290                mGridView.setSelectedPositionSmooth(200);
3291            }
3292        });
3293        waitForScrollIdle(mVerifyLayout);
3294        Thread.sleep(500);
3295        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
3296        assertEquals(top1, top7);
3297
3298        // scroll to invisible item that is far away.
3299        setSelectedPosition(10, 50);
3300        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
3301        assertEquals(top1 - 50, top8);
3302    }
3303
3304    @Test
3305    public void testSmoothScrollSelectionEvents() throws Throwable {
3306        Intent intent = new Intent();
3307        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3308                R.layout.vertical_grid);
3309        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
3310        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3311        mOrientation = BaseGridView.VERTICAL;
3312        mNumRows = 3;
3313        initActivity(intent);
3314
3315        mActivityTestRule.runOnUiThread(new Runnable() {
3316            @Override
3317            public void run() {
3318                mGridView.setSelectedPositionSmooth(30);
3319            }
3320        });
3321        waitForScrollIdle(mVerifyLayout);
3322        humanDelay(500);
3323
3324        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
3325        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
3326            @Override
3327            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
3328                selectedPositions.add(position);
3329            }
3330        });
3331
3332        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
3333        humanDelay(500);
3334        waitForScrollIdle(mVerifyLayout);
3335        // should only get childselected event for item 0 once
3336        assertTrue(selectedPositions.size() > 0);
3337        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
3338        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
3339            assertFalse(0 == selectedPositions.get(i).intValue());
3340        }
3341
3342    }
3343
3344    @Test
3345    public void testSmoothScrollSelectionEventsLinear() throws Throwable {
3346        Intent intent = new Intent();
3347        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3348                R.layout.vertical_linear);
3349        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
3350        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3351        mOrientation = BaseGridView.VERTICAL;
3352        mNumRows = 1;
3353        initActivity(intent);
3354
3355        mActivityTestRule.runOnUiThread(new Runnable() {
3356            @Override
3357            public void run() {
3358                mGridView.setSelectedPositionSmooth(10);
3359            }
3360        });
3361        waitForScrollIdle(mVerifyLayout);
3362        humanDelay(500);
3363
3364        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
3365        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
3366            @Override
3367            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
3368                selectedPositions.add(position);
3369            }
3370        });
3371
3372        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
3373        humanDelay(500);
3374        waitForScrollIdle(mVerifyLayout);
3375        // should only get childselected event for item 0 once
3376        assertTrue(selectedPositions.size() > 0);
3377        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
3378        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
3379            assertFalse(0 == selectedPositions.get(i).intValue());
3380        }
3381
3382    }
3383
3384    @Test
3385    public void testScrollToNoneExisting() throws Throwable {
3386        Intent intent = new Intent();
3387        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3388                R.layout.vertical_grid);
3389        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
3390        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3391        mOrientation = BaseGridView.VERTICAL;
3392        mNumRows = 3;
3393        initActivity(intent);
3394
3395        mActivityTestRule.runOnUiThread(new Runnable() {
3396            @Override
3397            public void run() {
3398                mGridView.setSelectedPositionSmooth(99);
3399            }
3400        });
3401        waitForScrollIdle(mVerifyLayout);
3402        humanDelay(500);
3403
3404
3405        mActivityTestRule.runOnUiThread(new Runnable() {
3406            @Override
3407            public void run() {
3408                mGridView.setSelectedPositionSmooth(50);
3409            }
3410        });
3411        Thread.sleep(100);
3412        mActivityTestRule.runOnUiThread(new Runnable() {
3413            @Override
3414            public void run() {
3415                mGridView.requestLayout();
3416                mGridView.setSelectedPositionSmooth(0);
3417            }
3418        });
3419        waitForScrollIdle(mVerifyLayout);
3420        humanDelay(500);
3421
3422    }
3423
3424    @Test
3425    public void testSmoothscrollerInterrupted() throws Throwable {
3426        Intent intent = new Intent();
3427        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3428                R.layout.vertical_linear);
3429        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3430        int[] items = new int[100];
3431        for (int i = 0; i < items.length; i++) {
3432            items[i] = 680;
3433        }
3434        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3435        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3436        mOrientation = BaseGridView.VERTICAL;
3437        mNumRows = 1;
3438
3439        initActivity(intent);
3440
3441        mGridView.setSelectedPositionSmooth(0);
3442        waitForScrollIdle(mVerifyLayout);
3443        assertTrue(mGridView.getChildAt(0).hasFocus());
3444
3445        // Pressing lots of key to make sure smooth scroller is running
3446        for (int i = 0; i < 20; i++) {
3447            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3448        }
3449        while (mGridView.getLayoutManager().isSmoothScrolling()
3450                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
3451            // Repeatedly pressing to make sure pending keys does not drop to zero.
3452            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3453        }
3454    }
3455
3456    @Test
3457    public void testSmoothscrollerCancelled() throws Throwable {
3458        Intent intent = new Intent();
3459        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3460                R.layout.vertical_linear);
3461        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3462        int[] items = new int[100];
3463        for (int i = 0; i < items.length; i++) {
3464            items[i] = 680;
3465        }
3466        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3467        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3468        mOrientation = BaseGridView.VERTICAL;
3469        mNumRows = 1;
3470
3471        initActivity(intent);
3472
3473        mGridView.setSelectedPositionSmooth(0);
3474        waitForScrollIdle(mVerifyLayout);
3475        assertTrue(mGridView.getChildAt(0).hasFocus());
3476
3477        int targetPosition = items.length - 1;
3478        mGridView.setSelectedPositionSmooth(targetPosition);
3479        mActivityTestRule.runOnUiThread(new Runnable() {
3480            @Override
3481            public void run() {
3482                mGridView.stopScroll();
3483            }
3484        });
3485        waitForScrollIdle();
3486        waitForItemAnimation();
3487        assertEquals(mGridView.getSelectedPosition(), targetPosition);
3488        assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
3489                mGridView.findFocus());
3490    }
3491
3492    @Test
3493    public void testSetNumRowsAndAddItem() throws Throwable {
3494        Intent intent = new Intent();
3495        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3496                R.layout.vertical_linear);
3497        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3498        int[] items = new int[2];
3499        for (int i = 0; i < items.length; i++) {
3500            items[i] = 300;
3501        }
3502        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3503        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3504        mOrientation = BaseGridView.VERTICAL;
3505        mNumRows = 1;
3506
3507        initActivity(intent);
3508
3509        mGridView.setSelectedPositionSmooth(0);
3510        waitForScrollIdle(mVerifyLayout);
3511
3512        mActivity.addItems(items.length, new int[]{300});
3513
3514        mActivityTestRule.runOnUiThread(new Runnable() {
3515            @Override
3516            public void run() {
3517                ((VerticalGridView) mGridView).setNumColumns(2);
3518            }
3519        });
3520        Thread.sleep(1000);
3521        assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft());
3522    }
3523
3524
3525    @Test
3526    public void testRequestLayoutBugInLayout() throws Throwable {
3527        Intent intent = new Intent();
3528        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3529                R.layout.vertical_linear);
3530        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
3531        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3532        int[] items = new int[100];
3533        for (int i = 0; i < items.length; i++) {
3534            items[i] = 300;
3535        }
3536        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3537        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3538        mOrientation = BaseGridView.VERTICAL;
3539        mNumRows = 1;
3540
3541        initActivity(intent);
3542
3543        mActivityTestRule.runOnUiThread(new Runnable() {
3544            @Override
3545            public void run() {
3546                mGridView.setSelectedPositionSmooth(1);
3547            }
3548        });
3549        waitForScrollIdle(mVerifyLayout);
3550
3551        sendKey(KeyEvent.KEYCODE_DPAD_UP);
3552        waitForScrollIdle(mVerifyLayout);
3553
3554        assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString());
3555    }
3556
3557
3558    @Test
3559    public void testChangeLayoutInChild() throws Throwable {
3560        Intent intent = new Intent();
3561        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3562                R.layout.vertical_linear_wrap_content);
3563        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
3564        int[] items = new int[2];
3565        for (int i = 0; i < items.length; i++) {
3566            items[i] = 300;
3567        }
3568        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3569        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3570        mOrientation = BaseGridView.VERTICAL;
3571        mNumRows = 1;
3572
3573        initActivity(intent);
3574
3575        mActivityTestRule.runOnUiThread(new Runnable() {
3576            @Override
3577            public void run() {
3578                mGridView.setSelectedPositionSmooth(0);
3579            }
3580        });
3581        waitForScrollIdle(mVerifyLayout);
3582        verifyMargin();
3583
3584        mActivityTestRule.runOnUiThread(new Runnable() {
3585            @Override
3586            public void run() {
3587                mGridView.setSelectedPositionSmooth(1);
3588            }
3589        });
3590        waitForScrollIdle(mVerifyLayout);
3591        verifyMargin();
3592    }
3593
3594    @Test
3595    public void testWrapContent() throws Throwable {
3596        Intent intent = new Intent();
3597        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3598                R.layout.horizontal_grid_wrap);
3599        int[] items = new int[200];
3600        for (int i = 0; i < items.length; i++) {
3601            items[i] = 300;
3602        }
3603        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3604        mOrientation = BaseGridView.HORIZONTAL;
3605        mNumRows = 1;
3606
3607        initActivity(intent);
3608
3609        mActivityTestRule.runOnUiThread(new Runnable() {
3610            @Override
3611            public void run() {
3612                mActivity.attachToNewAdapter(new int[0]);
3613            }
3614        });
3615
3616    }
3617
3618    @Test
3619    public void testZeroFixedSecondarySize() throws Throwable {
3620        Intent intent = new Intent();
3621        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3622                R.layout.vertical_linear_measured_with_zero);
3623        intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true);
3624        int[] items = new int[2];
3625        for (int i = 0; i < items.length; i++) {
3626            items[i] = 0;
3627        }
3628        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3629        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3630        mOrientation = BaseGridView.VERTICAL;
3631        mNumRows = 1;
3632
3633        initActivity(intent);
3634
3635    }
3636
3637    @Test
3638    public void testChildStates() throws Throwable {
3639        Intent intent = new Intent();
3640        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
3641        int[] items = new int[100];
3642        for (int i = 0; i < items.length; i++) {
3643            items[i] = 200;
3644        }
3645        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3646        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3647        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
3648        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
3649        mOrientation = BaseGridView.VERTICAL;
3650        mNumRows = 1;
3651
3652        initActivity(intent);
3653        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
3654
3655        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
3656
3657        // 1 Save view states
3658        mActivityTestRule.runOnUiThread(new Runnable() {
3659            @Override
3660            public void run() {
3661                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
3662                        .getText()), 0, 1);
3663                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
3664                        .getText()), 0, 1);
3665                mGridView.saveHierarchyState(container);
3666            }
3667        });
3668
3669        // 2 Change view states
3670        mActivityTestRule.runOnUiThread(new Runnable() {
3671            @Override
3672            public void run() {
3673                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
3674                        .getText()), 1, 2);
3675                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
3676                        .getText()), 1, 2);
3677            }
3678        });
3679
3680        // 3 Detached and re-attached,  should still maintain state of (2)
3681        mActivityTestRule.runOnUiThread(new Runnable() {
3682            @Override
3683            public void run() {
3684                mGridView.setSelectedPositionSmooth(1);
3685            }
3686        });
3687        waitForScrollIdle(mVerifyLayout);
3688        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
3689        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
3690        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
3691        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
3692
3693        // 4 Recycled and rebound, should load state from (2)
3694        mActivityTestRule.runOnUiThread(new Runnable() {
3695            @Override
3696            public void run() {
3697                mGridView.setSelectedPositionSmooth(20);
3698            }
3699        });
3700        waitForScrollIdle(mVerifyLayout);
3701        mActivityTestRule.runOnUiThread(new Runnable() {
3702            @Override
3703            public void run() {
3704                mGridView.setSelectedPositionSmooth(0);
3705            }
3706        });
3707        waitForScrollIdle(mVerifyLayout);
3708        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
3709        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
3710        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
3711        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
3712    }
3713
3714
3715    @Test
3716    public void testNoDispatchSaveChildState() throws Throwable {
3717        Intent intent = new Intent();
3718        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
3719        int[] items = new int[100];
3720        for (int i = 0; i < items.length; i++) {
3721            items[i] = 200;
3722        }
3723        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3724        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3725        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
3726        mOrientation = BaseGridView.VERTICAL;
3727        mNumRows = 1;
3728
3729        initActivity(intent);
3730        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD);
3731
3732        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
3733
3734        // 1. Set text selection, save view states should do nothing on child
3735        mActivityTestRule.runOnUiThread(new Runnable() {
3736            @Override
3737            public void run() {
3738                for (int i = 0; i < mGridView.getChildCount(); i++) {
3739                    Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i))
3740                            .getText()), 0, 1);
3741                }
3742                mGridView.saveHierarchyState(container);
3743            }
3744        });
3745
3746        // 2. clear the text selection
3747        mActivityTestRule.runOnUiThread(new Runnable() {
3748            @Override
3749            public void run() {
3750                for (int i = 0; i < mGridView.getChildCount(); i++) {
3751                    Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i))
3752                            .getText()));
3753                }
3754            }
3755        });
3756
3757        // 3. Restore view states should be a no-op for child
3758        mActivityTestRule.runOnUiThread(new Runnable() {
3759            @Override
3760            public void run() {
3761                mGridView.restoreHierarchyState(container);
3762                for (int i = 0; i < mGridView.getChildCount(); i++) {
3763                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart());
3764                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd());
3765                }
3766            }
3767        });
3768    }
3769
3770
3771    static interface ViewTypeProvider {
3772        public int getViewType(int position);
3773    }
3774
3775    static interface ItemAlignmentFacetProvider {
3776        public ItemAlignmentFacet getItemAlignmentFacet(int viewType);
3777    }
3778
3779    static class TwoViewTypesProvider implements ViewTypeProvider {
3780        static int VIEW_TYPE_FIRST = 1;
3781        static int VIEW_TYPE_DEFAULT = 0;
3782        @Override
3783        public int getViewType(int position) {
3784            if (position == 0) {
3785                return VIEW_TYPE_FIRST;
3786            } else {
3787                return VIEW_TYPE_DEFAULT;
3788            }
3789        }
3790    }
3791
3792    static class ChangeableViewTypesProvider implements ViewTypeProvider {
3793        static SparseIntArray sViewTypes = new SparseIntArray();
3794        @Override
3795        public int getViewType(int position) {
3796            return sViewTypes.get(position);
3797        }
3798        public static void clear() {
3799            sViewTypes.clear();
3800        }
3801        public static void setViewType(int position, int type) {
3802            sViewTypes.put(position, type);
3803        }
3804    }
3805
3806    static class PositionItemAlignmentFacetProviderForRelativeLayout1
3807            implements ItemAlignmentFacetProvider {
3808        ItemAlignmentFacet mMultipleFacet;
3809
3810        PositionItemAlignmentFacetProviderForRelativeLayout1() {
3811            mMultipleFacet = new ItemAlignmentFacet();
3812            ItemAlignmentFacet.ItemAlignmentDef[] defs =
3813                    new ItemAlignmentFacet.ItemAlignmentDef[2];
3814            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
3815            defs[0].setItemAlignmentViewId(R.id.t1);
3816            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
3817            defs[1].setItemAlignmentViewId(R.id.t2);
3818            defs[1].setItemAlignmentOffsetPercent(100);
3819            defs[1].setItemAlignmentOffset(-10);
3820            mMultipleFacet.setAlignmentDefs(defs);
3821        }
3822
3823        @Override
3824        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
3825            if (position == 0) {
3826                return mMultipleFacet;
3827            } else {
3828                return null;
3829            }
3830        }
3831    }
3832
3833    @Test
3834    public void testMultipleScrollPosition1() throws Throwable {
3835        Intent intent = new Intent();
3836        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
3837                R.layout.vertical_linear);
3838        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
3839        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3840        int[] items = new int[100];
3841        for (int i = 0; i < items.length; i++) {
3842            items[i] = 300;
3843        }
3844        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3845        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3846        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
3847                TwoViewTypesProvider.class.getName());
3848        // Set ItemAlignment for each ViewHolder and view type,  ViewHolder should
3849        // override the view type settings.
3850        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
3851                PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName());
3852        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
3853                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
3854        mOrientation = BaseGridView.VERTICAL;
3855        mNumRows = 1;
3856
3857        initActivity(intent);
3858
3859        assertEquals("First view is aligned with padding top",
3860                mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop());
3861
3862        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3863        waitForScrollIdle(mVerifyLayout);
3864
3865        final View v = mGridView.getChildAt(0);
3866        View t1 = v.findViewById(R.id.t1);
3867        int t1align = (t1.getTop() + t1.getBottom()) / 2;
3868        View t2 = v.findViewById(R.id.t2);
3869        int t2align = t2.getBottom() - 10;
3870        assertEquals("Expected alignment for 2nd textview",
3871                mGridView.getPaddingTop() - (t2align - t1align),
3872                v.getTop());
3873    }
3874
3875    static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements
3876            ItemAlignmentFacetProvider {
3877        ItemAlignmentFacet mMultipleFacet;
3878
3879        PositionItemAlignmentFacetProviderForRelativeLayout2() {
3880            mMultipleFacet = new ItemAlignmentFacet();
3881            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
3882            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
3883            defs[0].setItemAlignmentViewId(R.id.t1);
3884            defs[0].setItemAlignmentOffsetPercent(0);
3885            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
3886            defs[1].setItemAlignmentViewId(R.id.t2);
3887            defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
3888            defs[1].setItemAlignmentOffset(-10);
3889            mMultipleFacet.setAlignmentDefs(defs);
3890        }
3891
3892        @Override
3893        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
3894            if (position == 0) {
3895                return mMultipleFacet;
3896            } else {
3897                return null;
3898            }
3899        }
3900    }
3901
3902    @Test
3903    public void testMultipleScrollPosition2() throws Throwable {
3904        Intent intent = new Intent();
3905        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
3906        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
3907        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3908        int[] items = new int[100];
3909        for (int i = 0; i < items.length; i++) {
3910            items[i] = 300;
3911        }
3912        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3913        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3914        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
3915                TwoViewTypesProvider.class.getName());
3916        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
3917                PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
3918        mOrientation = BaseGridView.VERTICAL;
3919        mNumRows = 1;
3920
3921        initActivity(intent);
3922
3923        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
3924                mGridView.getChildAt(0).getTop());
3925
3926        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3927        waitForScrollIdle(mVerifyLayout);
3928
3929        final View v = mGridView.getChildAt(0);
3930        View t1 = v.findViewById(R.id.t1);
3931        int t1align = t1.getTop();
3932        View t2 = v.findViewById(R.id.t2);
3933        int t2align = t2.getTop() - 10;
3934        assertEquals("Expected alignment for 2nd textview",
3935                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
3936    }
3937
3938    static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements
3939            ItemAlignmentFacetProvider {
3940        ItemAlignmentFacet mMultipleFacet;
3941
3942        ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() {
3943            mMultipleFacet = new ItemAlignmentFacet();
3944            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
3945            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
3946            defs[0].setItemAlignmentViewId(R.id.t1);
3947            defs[0].setItemAlignmentOffsetPercent(0);
3948            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
3949            defs[1].setItemAlignmentViewId(R.id.t2);
3950            defs[1].setItemAlignmentOffsetPercent(100);
3951            defs[1].setItemAlignmentOffset(-10);
3952            mMultipleFacet.setAlignmentDefs(defs);
3953        }
3954
3955        @Override
3956        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
3957            if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) {
3958                return mMultipleFacet;
3959            } else {
3960                return null;
3961            }
3962        }
3963    }
3964
3965    @Test
3966    public void testMultipleScrollPosition3() throws Throwable {
3967        Intent intent = new Intent();
3968        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
3969        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
3970        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
3971        int[] items = new int[100];
3972        for (int i = 0; i < items.length; i++) {
3973            items[i] = 300;
3974        }
3975        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
3976        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
3977        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
3978                TwoViewTypesProvider.class.getName());
3979        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
3980                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
3981        mOrientation = BaseGridView.VERTICAL;
3982        mNumRows = 1;
3983
3984        initActivity(intent);
3985
3986        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
3987                mGridView.getChildAt(0).getTop());
3988
3989        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
3990        waitForScrollIdle(mVerifyLayout);
3991
3992        final View v = mGridView.getChildAt(0);
3993        View t1 = v.findViewById(R.id.t1);
3994        int t1align = t1.getTop();
3995        View t2 = v.findViewById(R.id.t2);
3996        int t2align = t2.getBottom() - 10;
3997        assertEquals("Expected alignment for 2nd textview",
3998                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
3999    }
4000
4001    @Test
4002    public void testSelectionAndAddItemInOneCycle() throws Throwable {
4003        Intent intent = new Intent();
4004        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4005                R.layout.vertical_linear);
4006        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
4007        initActivity(intent);
4008        mOrientation = BaseGridView.HORIZONTAL;
4009        mNumRows = 1;
4010
4011        performAndWaitForAnimation(new Runnable() {
4012            @Override
4013            public void run() {
4014                mActivity.addItems(0, new int[]{300, 300});
4015                mGridView.setSelectedPosition(0);
4016            }
4017        });
4018        assertEquals(0, mGridView.getSelectedPosition());
4019    }
4020
4021    @Test
4022    public void testSelectViewTaskSmoothWithAdapterChange() throws Throwable {
4023        testSelectViewTaskWithAdapterChange(true /*smooth*/);
4024    }
4025
4026    @Test
4027    public void testSelectViewTaskWithAdapterChange() throws Throwable {
4028        testSelectViewTaskWithAdapterChange(false /*smooth*/);
4029    }
4030
4031    private void testSelectViewTaskWithAdapterChange(final boolean smooth) throws Throwable {
4032        Intent intent = new Intent();
4033        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4034                R.layout.vertical_linear);
4035        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
4036        initActivity(intent);
4037        mOrientation = BaseGridView.HORIZONTAL;
4038        mNumRows = 1;
4039
4040        final View firstView = mGridView.getLayoutManager().findViewByPosition(0);
4041        final View[] selectedViewByTask = new View[1];
4042        final ViewHolderTask task = new ViewHolderTask() {
4043            @Override
4044            public void run(RecyclerView.ViewHolder viewHolder) {
4045                selectedViewByTask[0] = viewHolder.itemView;
4046            }
4047        };
4048        performAndWaitForAnimation(new Runnable() {
4049            @Override
4050            public void run() {
4051                mActivity.removeItems(0, 1);
4052                if (smooth) {
4053                    mGridView.setSelectedPositionSmooth(0, task);
4054                } else {
4055                    mGridView.setSelectedPosition(0, task);
4056                }
4057            }
4058        });
4059        assertEquals(0, mGridView.getSelectedPosition());
4060        assertNotNull(selectedViewByTask[0]);
4061        assertNotSame(firstView, selectedViewByTask[0]);
4062        assertSame(mGridView.getLayoutManager().findViewByPosition(0), selectedViewByTask[0]);
4063    }
4064
4065    @Test
4066    public void testNotifyItemTypeChangedSelectionEvent() throws Throwable {
4067        Intent intent = new Intent();
4068        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4069                R.layout.vertical_linear);
4070        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
4071        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
4072                ChangeableViewTypesProvider.class.getName());
4073        ChangeableViewTypesProvider.clear();
4074        initActivity(intent);
4075        mOrientation = BaseGridView.HORIZONTAL;
4076        mNumRows = 1;
4077
4078        final ArrayList<Integer> selectedLog = new ArrayList<Integer>();
4079        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
4080            @Override
4081            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
4082                selectedLog.add(position);
4083            }
4084        });
4085
4086        performAndWaitForAnimation(new Runnable() {
4087            @Override
4088            public void run() {
4089                ChangeableViewTypesProvider.setViewType(0, 1);
4090                mGridView.getAdapter().notifyItemChanged(0, 1);
4091            }
4092        });
4093        assertEquals(0, mGridView.getSelectedPosition());
4094        assertEquals(selectedLog.size(), 1);
4095        assertEquals((int) selectedLog.get(0), 0);
4096    }
4097
4098    @Test
4099    public void testNotifyItemChangedSelectionEvent() throws Throwable {
4100        Intent intent = new Intent();
4101        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4102                R.layout.vertical_linear);
4103        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
4104        initActivity(intent);
4105        mOrientation = BaseGridView.HORIZONTAL;
4106        mNumRows = 1;
4107
4108        OnChildViewHolderSelectedListener listener =
4109                Mockito.mock(OnChildViewHolderSelectedListener.class);
4110        mGridView.setOnChildViewHolderSelectedListener(listener);
4111
4112        performAndWaitForAnimation(new Runnable() {
4113            @Override
4114            public void run() {
4115                mGridView.getAdapter().notifyItemChanged(0, 1);
4116            }
4117        });
4118        Mockito.verify(listener, times(1)).onChildViewHolderSelected(any(RecyclerView.class),
4119                any(RecyclerView.ViewHolder.class), anyInt(), anyInt());
4120        assertEquals(0, mGridView.getSelectedPosition());
4121    }
4122
4123    @Test
4124    public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable {
4125        Intent intent = new Intent();
4126        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4127                R.layout.vertical_linear);
4128        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
4129        initActivity(intent);
4130        mOrientation = BaseGridView.HORIZONTAL;
4131        mNumRows = 1;
4132
4133        performAndWaitForAnimation(new Runnable() {
4134            @Override
4135            public void run() {
4136                mActivity.addItems(0, new int[]{300, 300});
4137                mGridView.setSelectedPositionSmooth(0);
4138            }
4139        });
4140        assertEquals(0, mGridView.getSelectedPosition());
4141    }
4142
4143    @Test
4144    public void testExtraLayoutSpace() throws Throwable {
4145        Intent intent = new Intent();
4146        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4147                R.layout.vertical_linear);
4148        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
4149        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4150        initActivity(intent);
4151
4152        final int windowSize = mGridView.getHeight();
4153        final int extraLayoutSize = windowSize;
4154        mOrientation = BaseGridView.VERTICAL;
4155        mNumRows = 1;
4156
4157        // add extra layout space
4158        startWaitLayout();
4159        mActivityTestRule.runOnUiThread(new Runnable() {
4160            @Override
4161            public void run() {
4162                mGridView.setExtraLayoutSpace(extraLayoutSize);
4163            }
4164        });
4165        waitForLayout();
4166        View v;
4167        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
4168        assertTrue(v.getTop() < windowSize + extraLayoutSize);
4169        assertTrue(v.getBottom() >= windowSize + extraLayoutSize - mGridView.getVerticalMargin());
4170
4171        mGridView.setSelectedPositionSmooth(150);
4172        waitForScrollIdle(mVerifyLayout);
4173        v = mGridView.getChildAt(0);
4174        assertTrue(v.getBottom() > - extraLayoutSize);
4175        assertTrue(v.getTop() <= -extraLayoutSize + mGridView.getVerticalMargin());
4176
4177        // clear extra layout space
4178        mActivityTestRule.runOnUiThread(new Runnable() {
4179            @Override
4180            public void run() {
4181                mGridView.setExtraLayoutSpace(0);
4182                verifyMargin();
4183            }
4184        });
4185        Thread.sleep(50);
4186        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
4187        assertTrue(v.getTop() < windowSize);
4188        assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
4189    }
4190
4191    @Test
4192    public void testFocusFinder() throws Throwable {
4193        Intent intent = new Intent();
4194        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4195                R.layout.vertical_linear_with_button);
4196        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
4197        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4198        initActivity(intent);
4199        mOrientation = BaseGridView.VERTICAL;
4200        mNumRows = 1;
4201
4202        // test focus from button to vertical grid view
4203        final View button = mActivity.findViewById(R.id.button);
4204        assertTrue(button.isFocused());
4205        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
4206        assertFalse(mGridView.isFocused());
4207        assertTrue(mGridView.hasFocus());
4208
4209        // FocusFinder should find last focused(2nd) item on DPAD_DOWN
4210        final View secondChild = mGridView.getChildAt(1);
4211        mActivityTestRule.runOnUiThread(new Runnable() {
4212            @Override
4213            public void run() {
4214                secondChild.requestFocus();
4215                button.requestFocus();
4216            }
4217        });
4218        assertTrue(button.isFocused());
4219        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
4220        assertTrue(secondChild.isFocused());
4221
4222        // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
4223        // (2nd) item on DPAD_DOWN.
4224        mActivityTestRule.runOnUiThread(new Runnable() {
4225            @Override
4226            public void run() {
4227                button.requestFocus();
4228            }
4229        });
4230        mGridView.setFocusable(false);
4231        mGridView.setFocusableInTouchMode(false);
4232        assertTrue(button.isFocused());
4233        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
4234        assertTrue(secondChild.isFocused());
4235    }
4236
4237    @Test
4238    public void testRestoreIndexAndAddItems() throws Throwable {
4239        Intent intent = new Intent();
4240        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4241                R.layout.vertical_linear);
4242        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
4243        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
4244        initActivity(intent);
4245        mOrientation = BaseGridView.VERTICAL;
4246        mNumRows = 1;
4247
4248        assertEquals(mGridView.getSelectedPosition(), 0);
4249        final SparseArray<Parcelable> states = new SparseArray<>();
4250        mActivityTestRule.runOnUiThread(new Runnable() {
4251            @Override
4252            public void run() {
4253                mGridView.saveHierarchyState(states);
4254                mGridView.setAdapter(null);
4255            }
4256
4257        });
4258        performAndWaitForAnimation(new Runnable() {
4259            @Override
4260            public void run() {
4261                mGridView.restoreHierarchyState(states);
4262                mActivity.attachToNewAdapter(new int[0]);
4263                mActivity.addItems(0, new int[]{100, 100, 100, 100});
4264            }
4265
4266        });
4267        assertEquals(mGridView.getSelectedPosition(), 0);
4268    }
4269
4270    @Test
4271    public void testRestoreIndexAndAddItemsSelect1() throws Throwable {
4272        Intent intent = new Intent();
4273        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4274                R.layout.vertical_linear);
4275        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
4276        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
4277        initActivity(intent);
4278        mOrientation = BaseGridView.VERTICAL;
4279        mNumRows = 1;
4280
4281        mActivityTestRule.runOnUiThread(new Runnable() {
4282            @Override
4283            public void run() {
4284                mGridView.setSelectedPosition(1);
4285            }
4286
4287        });
4288        assertEquals(mGridView.getSelectedPosition(), 1);
4289        final SparseArray<Parcelable> states = new SparseArray<>();
4290        mActivityTestRule.runOnUiThread(new Runnable() {
4291            @Override
4292            public void run() {
4293                mGridView.saveHierarchyState(states);
4294                mGridView.setAdapter(null);
4295            }
4296
4297        });
4298        performAndWaitForAnimation(new Runnable() {
4299            @Override
4300            public void run() {
4301                mGridView.restoreHierarchyState(states);
4302                mActivity.attachToNewAdapter(new int[0]);
4303                mActivity.addItems(0, new int[]{100, 100, 100, 100});
4304            }
4305
4306        });
4307        assertEquals(mGridView.getSelectedPosition(), 1);
4308    }
4309
4310    @Test
4311    public void testRestoreStateAfterAdapterChange() throws Throwable {
4312        Intent intent = new Intent();
4313        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4314                R.layout.vertical_linear);
4315        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
4316        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{50, 50, 50, 50});
4317        initActivity(intent);
4318        mOrientation = BaseGridView.VERTICAL;
4319        mNumRows = 1;
4320
4321        mActivityTestRule.runOnUiThread(new Runnable() {
4322            @Override
4323            public void run() {
4324                mGridView.setSelectedPosition(1);
4325                mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
4326            }
4327
4328        });
4329        assertEquals(mGridView.getSelectedPosition(), 1);
4330        final SparseArray<Parcelable> states = new SparseArray<>();
4331        mActivityTestRule.runOnUiThread(new Runnable() {
4332            @Override
4333            public void run() {
4334                Selection.setSelection((Spannable) (((TextView) mGridView.getChildAt(0))
4335                        .getText()), 1, 2);
4336                Selection.setSelection((Spannable) (((TextView) mGridView.getChildAt(1))
4337                        .getText()), 0, 1);
4338                mGridView.saveHierarchyState(states);
4339                mGridView.setAdapter(null);
4340            }
4341
4342        });
4343        performAndWaitForAnimation(new Runnable() {
4344            @Override
4345            public void run() {
4346                mGridView.restoreHierarchyState(states);
4347                mActivity.attachToNewAdapter(new int[]{50, 50, 50, 50});
4348            }
4349
4350        });
4351        assertEquals(mGridView.getSelectedPosition(), 1);
4352        assertEquals(1, ((TextView) mGridView.getChildAt(0)).getSelectionStart());
4353        assertEquals(2, ((TextView) mGridView.getChildAt(0)).getSelectionEnd());
4354        assertEquals(0, ((TextView) mGridView.getChildAt(1)).getSelectionStart());
4355        assertEquals(1, ((TextView) mGridView.getChildAt(1)).getSelectionEnd());
4356    }
4357
4358    @Test
4359    public void test27766012() throws Throwable {
4360        Intent intent = new Intent();
4361        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4362                R.layout.vertical_linear_with_button_onleft);
4363        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
4364        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
4365        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4366        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
4367        initActivity(intent);
4368        mOrientation = BaseGridView.VERTICAL;
4369        mNumRows = 1;
4370
4371        // set remove animator two seconds
4372        mGridView.getItemAnimator().setRemoveDuration(2000);
4373        final View view = mGridView.getChildAt(1);
4374        mActivityTestRule.runOnUiThread(new Runnable() {
4375            @Override
4376            public void run() {
4377                view.requestFocus();
4378            }
4379        });
4380        assertTrue(view.hasFocus());
4381        mActivityTestRule.runOnUiThread(new Runnable() {
4382            @Override
4383            public void run() {
4384                mActivity.removeItems(0, 2);
4385            }
4386
4387        });
4388        // wait one second, removing second view is still attached to parent
4389        Thread.sleep(1000);
4390        assertSame(view.getParent(), mGridView);
4391        mActivityTestRule.runOnUiThread(new Runnable() {
4392            @Override
4393            public void run() {
4394                // refocus to the removed item and do a focus search.
4395                view.requestFocus();
4396                view.focusSearch(View.FOCUS_UP);
4397            }
4398
4399        });
4400    }
4401
4402    @Test
4403    public void testBug27258366() throws Throwable {
4404        Intent intent = new Intent();
4405        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4406                R.layout.vertical_linear_with_button_onleft);
4407        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
4408        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
4409        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4410        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
4411        initActivity(intent);
4412        mOrientation = BaseGridView.VERTICAL;
4413        mNumRows = 1;
4414
4415        // move item1 500 pixels right, when focus is on item1, default focus finder will pick
4416        // item0 and item2 for the best match of focusSearch(FOCUS_LEFT).  The grid widget
4417        // must override default addFocusables(), not to add item0 or item2.
4418        mActivity.mAdapterListener = new GridActivity.AdapterListener() {
4419            @Override
4420            public void onBind(RecyclerView.ViewHolder vh, int position) {
4421                if (position == 1) {
4422                    vh.itemView.setPaddingRelative(500, 0, 0, 0);
4423                } else {
4424                    vh.itemView.setPaddingRelative(0, 0, 0, 0);
4425                }
4426            }
4427        };
4428        mActivityTestRule.runOnUiThread(new Runnable() {
4429            @Override
4430            public void run() {
4431                mGridView.getAdapter().notifyDataSetChanged();
4432            }
4433        });
4434        Thread.sleep(100);
4435
4436        final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1);
4437        mActivityTestRule.runOnUiThread(new Runnable() {
4438            @Override
4439            public void run() {
4440                secondChild.requestFocus();
4441            }
4442        });
4443        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
4444        Thread.sleep(100);
4445        final View button = mActivity.findViewById(R.id.button);
4446        assertTrue(button.isFocused());
4447    }
4448
4449    @Test
4450    public void testUpdateHeightScrollHorizontal() throws Throwable {
4451        Intent intent = new Intent();
4452        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4453                R.layout.horizontal_linear);
4454        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
4455        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4456        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
4457        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
4458        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, true);
4459        initActivity(intent);
4460        mOrientation = BaseGridView.HORIZONTAL;
4461        mNumRows = 1;
4462
4463        final int childTop = mGridView.getChildAt(0).getTop();
4464        // scroll to end, all children's top should not change.
4465        scrollToEnd(new Runnable() {
4466            @Override
4467            public void run() {
4468                for (int i = 0; i < mGridView.getChildCount(); i++) {
4469                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
4470                }
4471            }
4472        });
4473        // sanity check last child has focus with a larger height.
4474        assertTrue(mGridView.getChildAt(0).getHeight()
4475                < mGridView.getChildAt(mGridView.getChildCount() - 1).getHeight());
4476    }
4477
4478    @Test
4479    public void testUpdateWidthScrollHorizontal() throws Throwable {
4480        Intent intent = new Intent();
4481        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4482                R.layout.horizontal_linear);
4483        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
4484        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4485        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
4486        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
4487        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
4488        initActivity(intent);
4489        mOrientation = BaseGridView.HORIZONTAL;
4490        mNumRows = 1;
4491
4492        final int childTop = mGridView.getChildAt(0).getTop();
4493        // scroll to end, all children's top should not change.
4494        scrollToEnd(new Runnable() {
4495            @Override
4496            public void run() {
4497                for (int i = 0; i < mGridView.getChildCount(); i++) {
4498                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
4499                }
4500            }
4501        });
4502        // sanity check last child has focus with a larger width.
4503        assertTrue(mGridView.getChildAt(0).getWidth()
4504                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
4505        if (mGridView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
4506            assertEquals(mGridView.getPaddingLeft(),
4507                    mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
4508        } else {
4509            assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
4510                    mGridView.getChildAt(mGridView.getChildCount() - 1).getRight());
4511        }
4512    }
4513
4514    @Test
4515    public void testUpdateWidthScrollHorizontalRtl() throws Throwable {
4516        Intent intent = new Intent();
4517        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4518                R.layout.horizontal_linear_rtl);
4519        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
4520        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4521        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
4522        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
4523        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
4524        initActivity(intent);
4525        mOrientation = BaseGridView.HORIZONTAL;
4526        mNumRows = 1;
4527
4528        final int childTop = mGridView.getChildAt(0).getTop();
4529        // scroll to end, all children's top should not change.
4530        scrollToEnd(new Runnable() {
4531            @Override
4532            public void run() {
4533                for (int i = 0; i < mGridView.getChildCount(); i++) {
4534                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
4535                }
4536            }
4537        });
4538        // sanity check last child has focus with a larger width.
4539        assertTrue(mGridView.getChildAt(0).getWidth()
4540                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
4541        assertEquals(mGridView.getPaddingLeft(),
4542                mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
4543    }
4544
4545    @Test
4546    public void testAccessibility() throws Throwable {
4547        Intent intent = new Intent();
4548        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4549                R.layout.vertical_linear);
4550        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
4551        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4552        initActivity(intent);
4553        mOrientation = BaseGridView.VERTICAL;
4554        mNumRows = 1;
4555
4556        assertTrue(0 == mGridView.getSelectedPosition());
4557
4558        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
4559                .getCompatAccessibilityDelegate();
4560        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
4561        mActivityTestRule.runOnUiThread(new Runnable() {
4562            @Override
4563            public void run() {
4564                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4565            }
4566        });
4567        assertTrue("test sanity", info.isScrollable());
4568        mActivityTestRule.runOnUiThread(new Runnable() {
4569            @Override
4570            public void run() {
4571                delegateCompat.performAccessibilityAction(mGridView,
4572                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
4573            }
4574        });
4575        waitForScrollIdle(mVerifyLayout);
4576        int selectedPosition1 = mGridView.getSelectedPosition();
4577        assertTrue(0 < selectedPosition1);
4578
4579        mActivityTestRule.runOnUiThread(new Runnable() {
4580            @Override
4581            public void run() {
4582                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4583            }
4584        });
4585        assertTrue("test sanity", info.isScrollable());
4586        mActivityTestRule.runOnUiThread(new Runnable() {
4587            @Override
4588            public void run() {
4589                delegateCompat.performAccessibilityAction(mGridView,
4590                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
4591            }
4592        });
4593        waitForScrollIdle(mVerifyLayout);
4594        int selectedPosition2 = mGridView.getSelectedPosition();
4595        assertTrue(selectedPosition2 < selectedPosition1);
4596    }
4597
4598    @Test
4599    public void testAccessibilityScrollForwardHalfVisible() throws Throwable {
4600        Intent intent = new Intent();
4601        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
4602        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
4603        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
4604        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4605        initActivity(intent);
4606        mOrientation = BaseGridView.VERTICAL;
4607        mNumRows = 1;
4608
4609        int height = mGridView.getHeight() - mGridView.getPaddingTop()
4610                - mGridView.getPaddingBottom();
4611        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
4612        mActivityTestRule.runOnUiThread(new Runnable() {
4613            @Override
4614            public void run() {
4615                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
4616                mGridView.setWindowAlignmentOffset(100);
4617                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
4618                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
4619                mGridView.setItemAlignmentOffset(0);
4620                mGridView.setItemAlignmentOffsetPercent(BaseGridView
4621                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
4622            }
4623        });
4624        mActivity.addItems(0, new int[]{childHeight, childHeight});
4625        waitForItemAnimation();
4626        setSelectedPosition(0);
4627
4628        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
4629                .getCompatAccessibilityDelegate();
4630        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
4631        mActivityTestRule.runOnUiThread(new Runnable() {
4632            @Override
4633            public void run() {
4634                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4635            }
4636        });
4637        assertTrue("test sanity", info.isScrollable());
4638        mActivityTestRule.runOnUiThread(new Runnable() {
4639            @Override
4640            public void run() {
4641                delegateCompat.performAccessibilityAction(mGridView,
4642                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
4643            }
4644        });
4645        waitForScrollIdle(mVerifyLayout);
4646        assertEquals(1, mGridView.getSelectedPosition());
4647    }
4648
4649    @Test
4650    public void testAccessibilityWhenScrollDisabled() throws Throwable {
4651        Intent intent = new Intent();
4652        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
4653        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
4654        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS,  1000);
4655        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4656        initActivity(intent);
4657        mOrientation = BaseGridView.VERTICAL;
4658        mNumRows = 1;
4659
4660        setSelectedPosition(0);
4661
4662        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
4663                .getCompatAccessibilityDelegate();
4664        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
4665        mActivityTestRule.runOnUiThread(new Runnable() {
4666            @Override
4667            public void run() {
4668                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4669            }
4670        });
4671        mGridView.setScrollEnabled(false);
4672        mActivityTestRule.runOnUiThread(new Runnable() {
4673            @Override
4674            public void run() {
4675                for (int i  = 0; i < 100; i++) {
4676                    delegateCompat.performAccessibilityAction(mGridView,
4677                            AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
4678                }
4679            }
4680        });
4681        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
4682    }
4683
4684    private boolean hasAction(AccessibilityNodeInfoCompat info, Object action) {
4685        if (Build.VERSION.SDK_INT >= 21) {
4686            AccessibilityNodeInfoCompat.AccessibilityActionCompat convertedAction =
4687                    (AccessibilityNodeInfoCompat.AccessibilityActionCompat) action;
4688            return ((info.getActions() & convertedAction.getId()) != 0);
4689        } else {
4690            int convertedAction = (int) action;
4691            return ((info.getActions() & convertedAction) != 0);
4692        }
4693    }
4694
4695    private void setUpActivityForScrollingTest(final boolean isRTL, boolean isHorizontal,
4696            int numChildViews, boolean isSiblingViewVisible) throws Throwable {
4697        Intent intent = new Intent();
4698        int layout;
4699        if (isHorizontal) {
4700            layout = isRTL ? R.layout.horizontal_linear_rtl : R.layout.horizontal_linear;
4701        } else {
4702            layout = R.layout.vertical_linear;
4703        }
4704        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, layout);
4705        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
4706        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
4707        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4708        initActivity(intent);
4709        mOrientation = isHorizontal ? BaseGridView.HORIZONTAL : BaseGridView.VERTICAL;
4710        mNumRows = 1;
4711
4712        final int offset = (isSiblingViewVisible ? 2 : 1) * (isHorizontal
4713                ? mGridView.getHorizontalSpacing() : mGridView.getVerticalSpacing());
4714        final int childSize = (isHorizontal ? mGridView.getWidth() : mGridView.getHeight())
4715                - offset - (isHorizontal ? 2 * mGridView.getHorizontalSpacing() :
4716                mGridView.getVerticalSpacing());
4717        mActivityTestRule.runOnUiThread(new Runnable() {
4718            @Override
4719            public void run() {
4720                if (isRTL) {
4721                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
4722                }
4723                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
4724                mGridView.setWindowAlignmentOffset(offset);
4725                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
4726                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
4727                mGridView.setItemAlignmentOffset(0);
4728                mGridView.setItemAlignmentOffsetPercent(BaseGridView
4729                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
4730            }
4731        });
4732        int[] widthArrays = new int[numChildViews];
4733        Arrays.fill(widthArrays, childSize);
4734        mActivity.addItems(0, widthArrays);
4735    }
4736
4737    private void testScrollingAction(boolean isRTL, boolean isHorizontal) throws Throwable {
4738        waitForItemAnimation();
4739        setSelectedPosition(1);
4740        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
4741                .getCompatAccessibilityDelegate();
4742        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
4743        mActivityTestRule.runOnUiThread(new Runnable() {
4744            @Override
4745            public void run() {
4746                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4747            }
4748        });
4749        // We are currently focusing on item 1, calculating the direction to get me to item 0
4750        final AccessibilityNodeInfoCompat.AccessibilityActionCompat itemZeroDirection;
4751        if (isHorizontal) {
4752            itemZeroDirection = isRTL
4753                    ? AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT :
4754                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
4755        } else {
4756            itemZeroDirection =
4757                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
4758        }
4759        final int translatedItemZeroDirection = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
4760
4761        assertTrue("test sanity", info.isScrollable());
4762        if (Build.VERSION.SDK_INT >= 23) {
4763            assertTrue("test sanity", hasAction(info, itemZeroDirection));
4764        } else {
4765            assertTrue("test sanity", hasAction(info, translatedItemZeroDirection));
4766        }
4767
4768        mActivityTestRule.runOnUiThread(new Runnable() {
4769            @Override
4770            public void run() {
4771                if (Build.VERSION.SDK_INT >= 23) {
4772                    delegateCompat.performAccessibilityAction(mGridView, itemZeroDirection.getId(),
4773                            null);
4774                } else {
4775                    delegateCompat.performAccessibilityAction(mGridView,
4776                            translatedItemZeroDirection, null);
4777                }
4778            }
4779        });
4780        waitForScrollIdle(mVerifyLayout);
4781        assertEquals(0, mGridView.getSelectedPosition());
4782        setSelectedPosition(0);
4783        // We are at item 0, calculate the direction that lead us to the item 1
4784        final AccessibilityNodeInfoCompat.AccessibilityActionCompat itemOneDirection;
4785        if (isHorizontal) {
4786            itemOneDirection = isRTL
4787                    ? AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT
4788                    : AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
4789        } else {
4790            itemOneDirection =
4791                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
4792        }
4793        final int translatedItemOneDirection = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
4794
4795        mActivityTestRule.runOnUiThread(new Runnable() {
4796            @Override
4797            public void run() {
4798                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4799            }
4800        });
4801        if (Build.VERSION.SDK_INT >= 23) {
4802            assertTrue("test sanity", hasAction(info, itemOneDirection));
4803        } else {
4804            assertTrue("test sanity", hasAction(info, translatedItemOneDirection));
4805        }
4806        mActivityTestRule.runOnUiThread(new Runnable() {
4807            @Override
4808            public void run() {
4809                if (Build.VERSION.SDK_INT >= 23) {
4810                    delegateCompat.performAccessibilityAction(mGridView, itemOneDirection.getId(),
4811                            null);
4812                } else {
4813                    delegateCompat.performAccessibilityAction(mGridView, translatedItemOneDirection,
4814                            null);
4815                }
4816            }
4817        });
4818        waitForScrollIdle(mVerifyLayout);
4819        assertEquals(1, mGridView.getSelectedPosition());
4820    }
4821
4822    @Test
4823    public void testAccessibilityRespondToLeftRightInvisible() throws Throwable {
4824        boolean isRTL = false;
4825        boolean isHorizontal = true;
4826        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4827                false /* next child partially visible */);
4828        testScrollingAction(isRTL, isHorizontal);
4829    }
4830
4831    @Test
4832    public void testAccessibilityRespondToLeftRightPartiallyVisible() throws Throwable {
4833        boolean isRTL = false;
4834        boolean isHorizontal = true;
4835        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4836                true /* next child partially visible */);
4837        testScrollingAction(isRTL, isHorizontal);
4838    }
4839
4840    @Test
4841    public void testAccessibilityRespondToLeftRightRtlInvisible()
4842            throws Throwable {
4843        boolean isRTL = true;
4844        boolean isHorizontal = true;
4845        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4846                false /* next child partially visible */);
4847        testScrollingAction(isRTL, isHorizontal);
4848    }
4849
4850    @Test
4851    public void testAccessibilityRespondToLeftRightRtlPartiallyVisible() throws Throwable {
4852        boolean isRTL = true;
4853        boolean isHorizontal = true;
4854        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4855                true /* next child partially visible */);
4856        testScrollingAction(isRTL, isHorizontal);
4857    }
4858
4859    @Test
4860    public void testAccessibilityRespondToScrollUpDownActionInvisible() throws Throwable {
4861        boolean isRTL = false;
4862        boolean isHorizontal = false;
4863        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4864                false /* next child partially visible */);
4865        testScrollingAction(isRTL, isHorizontal);
4866    }
4867
4868    @Test
4869    public void testAccessibilityRespondToScrollUpDownActionPartiallyVisible() throws Throwable {
4870        boolean isRTL = false;
4871        boolean isHorizontal = false;
4872        setUpActivityForScrollingTest(isRTL, isHorizontal, 2 /* numChild */,
4873                true /* next child partially visible */);
4874        testScrollingAction(isRTL, isHorizontal);
4875    }
4876
4877    @Test
4878    public void testAccessibilityScrollBackwardHalfVisible() throws Throwable {
4879        Intent intent = new Intent();
4880        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
4881        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_top);
4882        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
4883        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4884        initActivity(intent);
4885        mOrientation = BaseGridView.VERTICAL;
4886        mNumRows = 1;
4887
4888        int height = mGridView.getHeight() - mGridView.getPaddingTop()
4889                - mGridView.getPaddingBottom();
4890        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
4891        mActivityTestRule.runOnUiThread(new Runnable() {
4892            @Override
4893            public void run() {
4894                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
4895                mGridView.setWindowAlignmentOffset(100);
4896                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
4897                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
4898                mGridView.setItemAlignmentOffset(0);
4899                mGridView.setItemAlignmentOffsetPercent(BaseGridView
4900                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
4901            }
4902        });
4903        mActivity.addItems(0, new int[]{childHeight, childHeight});
4904        waitForItemAnimation();
4905        setSelectedPosition(1);
4906
4907        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
4908                .getCompatAccessibilityDelegate();
4909        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
4910        mActivityTestRule.runOnUiThread(new Runnable() {
4911            @Override
4912            public void run() {
4913                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
4914            }
4915        });
4916        assertTrue("test sanity", info.isScrollable());
4917        mActivityTestRule.runOnUiThread(new Runnable() {
4918            @Override
4919            public void run() {
4920                delegateCompat.performAccessibilityAction(mGridView,
4921                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
4922            }
4923        });
4924        waitForScrollIdle(mVerifyLayout);
4925        assertEquals(0, mGridView.getSelectedPosition());
4926    }
4927
4928    void slideInAndWaitIdle() throws Throwable {
4929        slideInAndWaitIdle(5000);
4930    }
4931
4932    void slideInAndWaitIdle(long timeout) throws Throwable {
4933        // animateIn() would reset position
4934        mActivityTestRule.runOnUiThread(new Runnable() {
4935            @Override
4936            public void run() {
4937                mGridView.animateIn();
4938            }
4939        });
4940        PollingCheck.waitFor(timeout, new PollingCheck.PollingCheckCondition() {
4941            @Override
4942            public boolean canProceed() {
4943                return !mGridView.getLayoutManager().isSmoothScrolling()
4944                        && mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
4945            }
4946        });
4947    }
4948
4949    @Test
4950    public void testAnimateOutBlockScrollTo() throws Throwable {
4951        Intent intent = new Intent();
4952        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
4953                R.layout.vertical_linear_with_button_onleft);
4954        int[] items = new int[100];
4955        for (int i = 0; i < items.length; i++) {
4956            items[i] = 300;
4957        }
4958        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
4959        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
4960        mOrientation = BaseGridView.VERTICAL;
4961        mNumRows = 1;
4962
4963        initActivity(intent);
4964
4965        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
4966                mGridView.getChildAt(0).getTop());
4967
4968        mActivityTestRule.runOnUiThread(new Runnable() {
4969            @Override
4970            public void run() {
4971                mGridView.animateOut();
4972            }
4973        });
4974        // wait until sliding out.
4975        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
4976            @Override
4977            public boolean canProceed() {
4978                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
4979            }
4980        });
4981        // scrollToPosition() should not affect slideOut status
4982        mActivityTestRule.runOnUiThread(new Runnable() {
4983            @Override
4984            public void run() {
4985                mGridView.scrollToPosition(0);
4986            }
4987        });
4988        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
4989            @Override
4990            public boolean canProceed() {
4991                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
4992            }
4993        });
4994        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
4995                >= mGridView.getHeight());
4996
4997        slideInAndWaitIdle();
4998        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
4999                mGridView.getChildAt(0).getTop());
5000    }
5001
5002    @Test
5003    public void testAnimateOutBlockSmoothScrolling() throws Throwable {
5004        Intent intent = new Intent();
5005        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5006                R.layout.vertical_linear_with_button_onleft);
5007        int[] items = new int[30];
5008        for (int i = 0; i < items.length; i++) {
5009            items[i] = 300;
5010        }
5011        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5012        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5013        mOrientation = BaseGridView.VERTICAL;
5014        mNumRows = 1;
5015
5016        initActivity(intent);
5017
5018        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5019                mGridView.getChildAt(0).getTop());
5020
5021        mActivityTestRule.runOnUiThread(new Runnable() {
5022            @Override
5023            public void run() {
5024                mGridView.animateOut();
5025            }
5026        });
5027        // wait until sliding out.
5028        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5029            @Override
5030            public boolean canProceed() {
5031                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
5032            }
5033        });
5034        // smoothScrollToPosition() should not affect slideOut status
5035        mActivityTestRule.runOnUiThread(new Runnable() {
5036            @Override
5037            public void run() {
5038                mGridView.smoothScrollToPosition(29);
5039            }
5040        });
5041        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
5042            @Override
5043            public boolean canProceed() {
5044                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5045            }
5046        });
5047        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
5048                >= mGridView.getHeight());
5049
5050        slideInAndWaitIdle();
5051        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
5052        assertSame("Scrolled to last child",
5053                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
5054    }
5055
5056    @Test
5057    public void testAnimateOutBlockLongScrollTo() throws Throwable {
5058        Intent intent = new Intent();
5059        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5060                R.layout.vertical_linear_with_button_onleft);
5061        int[] items = new int[30];
5062        for (int i = 0; i < items.length; i++) {
5063            items[i] = 300;
5064        }
5065        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5066        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5067        mOrientation = BaseGridView.VERTICAL;
5068        mNumRows = 1;
5069
5070        initActivity(intent);
5071
5072        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5073                mGridView.getChildAt(0).getTop());
5074
5075        mActivityTestRule.runOnUiThread(new Runnable() {
5076            @Override
5077            public void run() {
5078                mGridView.animateOut();
5079            }
5080        });
5081        // wait until sliding out.
5082        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5083            @Override
5084            public boolean canProceed() {
5085                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
5086            }
5087        });
5088        // smoothScrollToPosition() should not affect slideOut status
5089        mActivityTestRule.runOnUiThread(new Runnable() {
5090            @Override
5091            public void run() {
5092                mGridView.scrollToPosition(29);
5093            }
5094        });
5095        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
5096            @Override
5097            public boolean canProceed() {
5098                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5099            }
5100        });
5101        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
5102                >= mGridView.getHeight());
5103
5104        slideInAndWaitIdle();
5105        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
5106        assertSame("Scrolled to last child",
5107                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
5108    }
5109
5110    @Test
5111    public void testAnimateOutBlockLayout() throws Throwable {
5112        Intent intent = new Intent();
5113        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5114                R.layout.vertical_linear_with_button_onleft);
5115        int[] items = new int[100];
5116        for (int i = 0; i < items.length; i++) {
5117            items[i] = 300;
5118        }
5119        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5120        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5121        mOrientation = BaseGridView.VERTICAL;
5122        mNumRows = 1;
5123
5124        initActivity(intent);
5125
5126        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5127                mGridView.getChildAt(0).getTop());
5128
5129        mActivityTestRule.runOnUiThread(new Runnable() {
5130            @Override
5131            public void run() {
5132                mGridView.animateOut();
5133            }
5134        });
5135        // wait until sliding out.
5136        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5137            @Override
5138            public boolean canProceed() {
5139                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
5140            }
5141        });
5142        // change adapter should not affect slideOut status
5143        mActivityTestRule.runOnUiThread(new Runnable() {
5144            @Override
5145            public void run() {
5146                mActivity.changeItem(0, 200);
5147            }
5148        });
5149        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5150            @Override
5151            public boolean canProceed() {
5152                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5153            }
5154        });
5155        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
5156                >= mGridView.getHeight());
5157        assertEquals("onLayout suppressed during slide out", 300,
5158                mGridView.getChildAt(0).getHeight());
5159
5160        slideInAndWaitIdle();
5161        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5162                mGridView.getChildAt(0).getTop());
5163        // size of item should be updated immediately after slide in animation finishes:
5164        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
5165            @Override
5166            public boolean canProceed() {
5167                return 200 == mGridView.getChildAt(0).getHeight();
5168            }
5169        });
5170    }
5171
5172    @Test
5173    public void testAnimateOutBlockFocusChange() throws Throwable {
5174        Intent intent = new Intent();
5175        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5176                R.layout.vertical_linear_with_button_onleft);
5177        int[] items = new int[100];
5178        for (int i = 0; i < items.length; i++) {
5179            items[i] = 300;
5180        }
5181        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5182        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5183        mOrientation = BaseGridView.VERTICAL;
5184        mNumRows = 1;
5185
5186        initActivity(intent);
5187
5188        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5189                mGridView.getChildAt(0).getTop());
5190
5191        mActivityTestRule.runOnUiThread(new Runnable() {
5192            @Override
5193            public void run() {
5194                mGridView.animateOut();
5195                mActivity.findViewById(R.id.button).requestFocus();
5196            }
5197        });
5198        assertTrue(mActivity.findViewById(R.id.button).hasFocus());
5199        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5200            @Override
5201            public boolean canProceed() {
5202                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
5203            }
5204        });
5205        mActivityTestRule.runOnUiThread(new Runnable() {
5206            @Override
5207            public void run() {
5208                mGridView.requestFocus();
5209            }
5210        });
5211        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5212            @Override
5213            public boolean canProceed() {
5214                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5215            }
5216        });
5217        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
5218                >= mGridView.getHeight());
5219
5220        slideInAndWaitIdle();
5221        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
5222                mGridView.getChildAt(0).getTop());
5223    }
5224
5225    @Test
5226    public void testHorizontalAnimateOutBlockScrollTo() throws Throwable {
5227        Intent intent = new Intent();
5228        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5229                R.layout.horizontal_linear);
5230        int[] items = new int[100];
5231        for (int i = 0; i < items.length; i++) {
5232            items[i] = 300;
5233        }
5234        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5235        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5236        mOrientation = BaseGridView.HORIZONTAL;
5237        mNumRows = 1;
5238
5239        initActivity(intent);
5240
5241        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
5242                mGridView.getChildAt(0).getLeft());
5243
5244        mActivityTestRule.runOnUiThread(new Runnable() {
5245            @Override
5246            public void run() {
5247                mGridView.animateOut();
5248            }
5249        });
5250        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5251            @Override
5252            public boolean canProceed() {
5253                return mGridView.getChildAt(0).getLeft() > mGridView.getPaddingLeft();
5254            }
5255        });
5256        mActivityTestRule.runOnUiThread(new Runnable() {
5257            @Override
5258            public void run() {
5259                mGridView.scrollToPosition(0);
5260            }
5261        });
5262        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5263            @Override
5264            public boolean canProceed() {
5265                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5266            }
5267        });
5268
5269        assertTrue("First view is slided out", mGridView.getChildAt(0).getLeft()
5270                > mGridView.getWidth());
5271
5272        slideInAndWaitIdle();
5273        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
5274                mGridView.getChildAt(0).getLeft());
5275
5276    }
5277
5278    @Test
5279    public void testHorizontalAnimateOutRtl() throws Throwable {
5280        Intent intent = new Intent();
5281        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5282                R.layout.horizontal_linear_rtl);
5283        int[] items = new int[100];
5284        for (int i = 0; i < items.length; i++) {
5285            items[i] = 300;
5286        }
5287        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5288        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5289        mOrientation = BaseGridView.HORIZONTAL;
5290        mNumRows = 1;
5291
5292        initActivity(intent);
5293
5294        assertEquals("First view is aligned with padding right",
5295                mGridView.getWidth() - mGridView.getPaddingRight(),
5296                mGridView.getChildAt(0).getRight());
5297
5298        mActivityTestRule.runOnUiThread(new Runnable() {
5299            @Override
5300            public void run() {
5301                mGridView.animateOut();
5302            }
5303        });
5304        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5305            @Override
5306            public boolean canProceed() {
5307                return mGridView.getChildAt(0).getRight()
5308                        < mGridView.getWidth() - mGridView.getPaddingRight();
5309            }
5310        });
5311        mActivityTestRule.runOnUiThread(new Runnable() {
5312            @Override
5313            public void run() {
5314                mGridView.smoothScrollToPosition(0);
5315            }
5316        });
5317        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
5318            @Override
5319            public boolean canProceed() {
5320                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
5321            }
5322        });
5323
5324        assertTrue("First view is slided out", mGridView.getChildAt(0).getRight() < 0);
5325
5326        slideInAndWaitIdle();
5327        assertEquals("First view is aligned with padding right",
5328                mGridView.getWidth() - mGridView.getPaddingRight(),
5329                mGridView.getChildAt(0).getRight());
5330    }
5331
5332    @Test
5333    public void testSmoothScrollerOutRange() throws Throwable {
5334        Intent intent = new Intent();
5335        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
5336                R.layout.vertical_linear_with_button_onleft);
5337        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
5338        int[] items = new int[30];
5339        for (int i = 0; i < items.length; i++) {
5340            items[i] = 680;
5341        }
5342        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5343        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5344        mOrientation = BaseGridView.VERTICAL;
5345        mNumRows = 1;
5346
5347        initActivity(intent);
5348
5349        final View button = mActivity.findViewById(R.id.button);
5350        mActivityTestRule.runOnUiThread(new Runnable() {
5351            public void run() {
5352                button.requestFocus();
5353            }
5354        });
5355
5356        mGridView.setSelectedPositionSmooth(0);
5357        waitForScrollIdle(mVerifyLayout);
5358
5359        mActivityTestRule.runOnUiThread(new Runnable() {
5360            public void run() {
5361                mGridView.setSelectedPositionSmooth(120);
5362            }
5363        });
5364        waitForScrollIdle(mVerifyLayout);
5365        assertTrue(button.hasFocus());
5366        int key;
5367        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
5368            key = KeyEvent.KEYCODE_DPAD_LEFT;
5369        } else {
5370            key = KeyEvent.KEYCODE_DPAD_RIGHT;
5371        }
5372        sendKey(key);
5373        // the GridView should has focus in its children
5374        assertTrue(mGridView.hasFocus());
5375        assertFalse(mGridView.isFocused());
5376        assertEquals(29, mGridView.getSelectedPosition());
5377    }
5378
5379    @Test
5380    public void testRemoveLastItemWithStableId() throws Throwable {
5381        Intent intent = new Intent();
5382        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5383        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
5384        int[] items = new int[1];
5385        for (int i = 0; i < items.length; i++) {
5386            items[i] = 680;
5387        }
5388        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5389        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5390        mOrientation = BaseGridView.VERTICAL;
5391        mNumRows = 1;
5392
5393        initActivity(intent);
5394
5395        mActivityTestRule.runOnUiThread(new Runnable() {
5396            @Override
5397            public void run() {
5398                mGridView.getItemAnimator().setRemoveDuration(2000);
5399                mActivity.removeItems(0, 1, false);
5400                mGridView.getAdapter().notifyDataSetChanged();
5401            }
5402        });
5403        Thread.sleep(500);
5404        assertEquals(-1, mGridView.getSelectedPosition());
5405    }
5406
5407    @Test
5408    public void testUpdateAndSelect1() throws Throwable {
5409        Intent intent = new Intent();
5410        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5411        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
5412        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
5413        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5414        mOrientation = BaseGridView.VERTICAL;
5415        mNumRows = 1;
5416
5417        initActivity(intent);
5418
5419        mActivityTestRule.runOnUiThread(new Runnable() {
5420            @Override
5421            public void run() {
5422                mGridView.getAdapter().notifyDataSetChanged();
5423                mGridView.setSelectedPosition(1);
5424            }
5425        });
5426        waitOneUiCycle();
5427        assertEquals(1, mGridView.getSelectedPosition());
5428    }
5429
5430    @Test
5431    public void testUpdateAndSelect2() throws Throwable {
5432        Intent intent = new Intent();
5433        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5434        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
5435        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
5436        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5437        mOrientation = BaseGridView.VERTICAL;
5438        mNumRows = 1;
5439
5440        initActivity(intent);
5441
5442        mActivityTestRule.runOnUiThread(new Runnable() {
5443            @Override
5444            public void run() {
5445                mGridView.getAdapter().notifyDataSetChanged();
5446                mGridView.setSelectedPosition(50);
5447            }
5448        });
5449        waitOneUiCycle();
5450        assertEquals(50, mGridView.getSelectedPosition());
5451    }
5452
5453    @Test
5454    public void testUpdateAndSelect3() throws Throwable {
5455        Intent intent = new Intent();
5456        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5457        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
5458        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
5459        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5460        mOrientation = BaseGridView.VERTICAL;
5461        mNumRows = 1;
5462
5463        initActivity(intent);
5464
5465        mActivityTestRule.runOnUiThread(new Runnable() {
5466            @Override
5467            public void run() {
5468                int[] newItems = new int[100];
5469                for (int i = 0; i < newItems.length; i++) {
5470                    newItems[i] = mActivity.mItemLengths[0];
5471                }
5472                mActivity.addItems(0, newItems, false);
5473                mGridView.getAdapter().notifyDataSetChanged();
5474                mGridView.setSelectedPosition(50);
5475            }
5476        });
5477        waitOneUiCycle();
5478        assertEquals(50, mGridView.getSelectedPosition());
5479    }
5480
5481    @Test
5482    public void testFocusedPositonAfterRemoved1() throws Throwable {
5483        Intent intent = new Intent();
5484        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5485        final int[] items = new int[2];
5486        for (int i = 0; i < items.length; i++) {
5487            items[i] = 300;
5488        }
5489        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5490        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5491        mOrientation = BaseGridView.VERTICAL;
5492        mNumRows = 1;
5493
5494        initActivity(intent);
5495        setSelectedPosition(1);
5496        assertEquals(1, mGridView.getSelectedPosition());
5497
5498        final int[] newItems = new int[3];
5499        for (int i = 0; i < newItems.length; i++) {
5500            newItems[i] = 300;
5501        }
5502        performAndWaitForAnimation(new Runnable() {
5503            @Override
5504            public void run() {
5505                mActivity.removeItems(0, 2, true);
5506                mActivity.addItems(0, newItems, true);
5507            }
5508        });
5509        assertEquals(0, mGridView.getSelectedPosition());
5510    }
5511
5512    @Test
5513    public void testFocusedPositonAfterRemoved2() throws Throwable {
5514        Intent intent = new Intent();
5515        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5516        final int[] items = new int[2];
5517        for (int i = 0; i < items.length; i++) {
5518            items[i] = 300;
5519        }
5520        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5521        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5522        mOrientation = BaseGridView.VERTICAL;
5523        mNumRows = 1;
5524
5525        initActivity(intent);
5526        setSelectedPosition(1);
5527        assertEquals(1, mGridView.getSelectedPosition());
5528
5529        final int[] newItems = new int[3];
5530        for (int i = 0; i < newItems.length; i++) {
5531            newItems[i] = 300;
5532        }
5533        performAndWaitForAnimation(new Runnable() {
5534            @Override
5535            public void run() {
5536                mActivity.removeItems(1, 1, true);
5537                mActivity.addItems(1, newItems, true);
5538            }
5539        });
5540        assertEquals(1, mGridView.getSelectedPosition());
5541    }
5542
5543    static void assertNoCollectionItemInfo(AccessibilityNodeInfoCompat info) {
5544        AccessibilityNodeInfoCompat.CollectionItemInfoCompat nodeInfoCompat =
5545                info.getCollectionItemInfo();
5546        if (nodeInfoCompat == null) {
5547            return;
5548        }
5549        assertTrue(nodeInfoCompat.getRowIndex() < 0);
5550        assertTrue(nodeInfoCompat.getColumnIndex() < 0);
5551    }
5552
5553    /**
5554     * This test would need talkback on.
5555     */
5556    @Test
5557    public void testAccessibilityOfItemsBeingPushedOut() throws Throwable {
5558        Intent intent = new Intent();
5559        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
5560        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
5561        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5562        mOrientation = BaseGridView.HORIZONTAL;
5563        mNumRows = 3;
5564
5565        initActivity(intent);
5566
5567        final int lastPos = mGridView.getChildAdapterPosition(
5568                mGridView.getChildAt(mGridView.getChildCount() - 1));
5569        mActivityTestRule.runOnUiThread(new Runnable() {
5570            @Override
5571            public void run() {
5572                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
5573            }
5574        });
5575        final int numItemsToPushOut = mNumRows;
5576        mActivityTestRule.runOnUiThread(new Runnable() {
5577            @Override
5578            public void run() {
5579                // set longer enough so that accessibility service will initialize node
5580                // within setImportantForAccessibility().
5581                mGridView.getItemAnimator().setRemoveDuration(2000);
5582                mGridView.getItemAnimator().setAddDuration(2000);
5583                final int[] newItems = new int[numItemsToPushOut];
5584                final int newItemValue = mActivity.mItemLengths[0];
5585                for (int i = 0; i < newItems.length; i++) {
5586                    newItems[i] = newItemValue;
5587                }
5588                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
5589            }
5590        });
5591        waitForItemAnimation();
5592    }
5593
5594    /**
5595     * This test simulates talkback by calling setImportanceForAccessibility at end of animation
5596     */
5597    @Test
5598    public void simulatesAccessibilityOfItemsBeingPushedOut() throws Throwable {
5599        Intent intent = new Intent();
5600        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
5601        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
5602        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5603        mOrientation = BaseGridView.HORIZONTAL;
5604        mNumRows = 3;
5605
5606        initActivity(intent);
5607
5608        final HashSet<View> moveAnimationViews = new HashSet();
5609        mActivity.mImportantForAccessibilityListener =
5610                new GridActivity.ImportantForAccessibilityListener() {
5611            RecyclerView.LayoutManager mLM = mGridView.getLayoutManager();
5612            @Override
5613            public void onImportantForAccessibilityChanged(View view, int newValue) {
5614                // simulates talkack, having setImportantForAccessibility to call
5615                // onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items.
5616                if (moveAnimationViews.contains(view)) {
5617                    AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
5618                    mLM.onInitializeAccessibilityNodeInfoForItem(
5619                            null, null, view, info);
5620                }
5621            }
5622        };
5623        final int lastPos = mGridView.getChildAdapterPosition(
5624                mGridView.getChildAt(mGridView.getChildCount() - 1));
5625        final int numItemsToPushOut = mNumRows;
5626        for (int i = 0; i < numItemsToPushOut; i++) {
5627            moveAnimationViews.add(
5628                    mGridView.getChildAt(mGridView.getChildCount() - 1 - i));
5629        }
5630        mActivityTestRule.runOnUiThread(new Runnable() {
5631            @Override
5632            public void run() {
5633                mGridView.setItemAnimator(new DefaultItemAnimator() {
5634                    @Override
5635                    public void onMoveFinished(RecyclerView.ViewHolder item) {
5636                        moveAnimationViews.remove(item.itemView);
5637                    }
5638                });
5639                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
5640            }
5641        });
5642        mActivityTestRule.runOnUiThread(new Runnable() {
5643            @Override
5644            public void run() {
5645                final int[] newItems = new int[numItemsToPushOut];
5646                final int newItemValue = mActivity.mItemLengths[0] + 1;
5647                for (int i = 0; i < newItems.length; i++) {
5648                    newItems[i] = newItemValue;
5649                }
5650                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
5651            }
5652        });
5653        while (moveAnimationViews.size() != 0) {
5654            Thread.sleep(100);
5655        }
5656    }
5657
5658    @Test
5659    public void testAccessibilityNodeInfoOnRemovedFirstItem() throws Throwable {
5660        Intent intent = new Intent();
5661        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
5662        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
5663        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5664        mOrientation = BaseGridView.HORIZONTAL;
5665        mNumRows = 3;
5666
5667        initActivity(intent);
5668
5669        final View lastView = mGridView.findViewHolderForAdapterPosition(0).itemView;
5670        mActivityTestRule.runOnUiThread(new Runnable() {
5671            @Override
5672            public void run() {
5673                mGridView.getItemAnimator().setRemoveDuration(20000);
5674                mActivity.removeItems(0, 1);
5675            }
5676        });
5677        waitForItemAnimationStart();
5678        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
5679        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
5680                lastView, info);
5681        assertNoCollectionItemInfo(info);
5682    }
5683
5684    @Test
5685    public void testAccessibilityNodeInfoOnRemovedLastItem() throws Throwable {
5686        Intent intent = new Intent();
5687        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
5688        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
5689        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5690        mOrientation = BaseGridView.HORIZONTAL;
5691        mNumRows = 3;
5692
5693        initActivity(intent);
5694
5695        final View lastView = mGridView.findViewHolderForAdapterPosition(5).itemView;
5696        mActivityTestRule.runOnUiThread(new Runnable() {
5697            @Override
5698            public void run() {
5699                mGridView.getItemAnimator().setRemoveDuration(20000);
5700                mActivity.removeItems(5, 1);
5701            }
5702        });
5703        waitForItemAnimationStart();
5704        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
5705        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
5706                lastView, info);
5707        assertNoCollectionItemInfo(info);
5708    }
5709
5710    static class FiveViewTypesProvider implements ViewTypeProvider {
5711
5712        @Override
5713        public int getViewType(int position) {
5714            switch (position) {
5715                case 0:
5716                    return 0;
5717                case 1:
5718                    return 1;
5719                case 2:
5720                    return 2;
5721                case 3:
5722                    return 3;
5723                case 4:
5724                    return 4;
5725            }
5726            return 199;
5727        }
5728    }
5729
5730    // Used by testItemAlignmentVertical() testItemAlignmentHorizontal()
5731    static class ItemAlignmentWithPaddingFacetProvider implements
5732            ItemAlignmentFacetProvider {
5733        final ItemAlignmentFacet mFacet0;
5734        final ItemAlignmentFacet mFacet1;
5735        final ItemAlignmentFacet mFacet2;
5736        final ItemAlignmentFacet mFacet3;
5737        final ItemAlignmentFacet mFacet4;
5738
5739        ItemAlignmentWithPaddingFacetProvider() {
5740            ItemAlignmentFacet.ItemAlignmentDef[] defs;
5741            mFacet0 = new ItemAlignmentFacet();
5742            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
5743            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
5744            defs[0].setItemAlignmentViewId(R.id.t1);
5745            defs[0].setItemAlignmentOffsetPercent(0);
5746            defs[0].setItemAlignmentOffsetWithPadding(false);
5747            mFacet0.setAlignmentDefs(defs);
5748            mFacet1 = new ItemAlignmentFacet();
5749            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
5750            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
5751            defs[0].setItemAlignmentViewId(R.id.t1);
5752            defs[0].setItemAlignmentOffsetPercent(0);
5753            defs[0].setItemAlignmentOffsetWithPadding(true);
5754            mFacet1.setAlignmentDefs(defs);
5755            mFacet2 = new ItemAlignmentFacet();
5756            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
5757            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
5758            defs[0].setItemAlignmentViewId(R.id.t2);
5759            defs[0].setItemAlignmentOffsetPercent(100);
5760            defs[0].setItemAlignmentOffsetWithPadding(true);
5761            mFacet2.setAlignmentDefs(defs);
5762            mFacet3 = new ItemAlignmentFacet();
5763            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
5764            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
5765            defs[0].setItemAlignmentViewId(R.id.t2);
5766            defs[0].setItemAlignmentOffsetPercent(50);
5767            defs[0].setItemAlignmentOffsetWithPadding(true);
5768            mFacet3.setAlignmentDefs(defs);
5769            mFacet4 = new ItemAlignmentFacet();
5770            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
5771            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
5772            defs[0].setItemAlignmentViewId(R.id.t2);
5773            defs[0].setItemAlignmentOffsetPercent(50);
5774            defs[0].setItemAlignmentOffsetWithPadding(false);
5775            mFacet4.setAlignmentDefs(defs);
5776        }
5777
5778        @Override
5779        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
5780            switch (viewType) {
5781                case 0:
5782                    return mFacet0;
5783                case 1:
5784                    return mFacet1;
5785                case 2:
5786                    return mFacet2;
5787                case 3:
5788                    return mFacet3;
5789                case 4:
5790                    return mFacet4;
5791            }
5792            return null;
5793        }
5794    }
5795
5796    @Test
5797    public void testItemAlignmentVertical() throws Throwable {
5798        Intent intent = new Intent();
5799        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
5800        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout2);
5801        int[] items = new int[5];
5802        for (int i = 0; i < items.length; i++) {
5803            items[i] = 300;
5804        }
5805        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5806        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5807        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
5808                FiveViewTypesProvider.class.getName());
5809        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
5810                ItemAlignmentWithPaddingFacetProvider.class.getName());
5811        mOrientation = BaseGridView.VERTICAL;
5812        mNumRows = 1;
5813
5814        initActivity(intent);
5815        startWaitLayout();
5816        mActivityTestRule.runOnUiThread(new Runnable() {
5817            @Override
5818            public void run() {
5819                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
5820                mGridView.setWindowAlignmentOffsetPercent(50);
5821                mGridView.setWindowAlignmentOffset(0);
5822            }
5823        });
5824        waitForLayout();
5825
5826        final float windowAlignCenter = mGridView.getHeight() / 2f;
5827        Rect rect = new Rect();
5828        View textView;
5829
5830        // test 1: does not include padding
5831        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
5832        rect.set(0, 0, textView.getWidth(), textView.getHeight());
5833        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5834        assertEquals(windowAlignCenter, rect.top, DELTA);
5835
5836        // test 2: including low padding
5837        setSelectedPosition(1);
5838        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
5839        assertTrue(textView.getPaddingTop() > 0);
5840        rect.set(0, textView.getPaddingTop(), textView.getWidth(), textView.getHeight());
5841        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5842        assertEquals(windowAlignCenter, rect.top, DELTA);
5843
5844        // test 3: including high padding
5845        setSelectedPosition(2);
5846        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
5847        assertTrue(textView.getPaddingBottom() > 0);
5848        rect.set(0, 0, textView.getWidth(),
5849                textView.getHeight() - textView.getPaddingBottom());
5850        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5851        assertEquals(windowAlignCenter, rect.bottom, DELTA);
5852
5853        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
5854        setSelectedPosition(3);
5855        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
5856        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
5857        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
5858        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5859        assertEquals(windowAlignCenter, rect.bottom, DELTA);
5860
5861        // test 5: does not include padding
5862        setSelectedPosition(4);
5863        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
5864        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
5865        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
5866        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5867        assertEquals(windowAlignCenter, rect.bottom, DELTA);
5868    }
5869
5870    @Test
5871    public void testItemAlignmentHorizontal() throws Throwable {
5872        Intent intent = new Intent();
5873        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
5874        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
5875        int[] items = new int[5];
5876        for (int i = 0; i < items.length; i++) {
5877            items[i] = 300;
5878        }
5879        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5880        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5881        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
5882                FiveViewTypesProvider.class.getName());
5883        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
5884                ItemAlignmentWithPaddingFacetProvider.class.getName());
5885        mOrientation = BaseGridView.VERTICAL;
5886        mNumRows = 1;
5887
5888        initActivity(intent);
5889        startWaitLayout();
5890        mActivityTestRule.runOnUiThread(new Runnable() {
5891            @Override
5892            public void run() {
5893                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
5894                mGridView.setWindowAlignmentOffsetPercent(50);
5895                mGridView.setWindowAlignmentOffset(0);
5896            }
5897        });
5898        waitForLayout();
5899
5900        final float windowAlignCenter = mGridView.getWidth() / 2f;
5901        Rect rect = new Rect();
5902        View textView;
5903
5904        // test 1: does not include padding
5905        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
5906        rect.set(0, 0, textView.getWidth(), textView.getHeight());
5907        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5908        assertEquals(windowAlignCenter, rect.left, DELTA);
5909
5910        // test 2: including low padding
5911        setSelectedPosition(1);
5912        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
5913        assertTrue(textView.getPaddingLeft() > 0);
5914        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(), textView.getHeight());
5915        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5916        assertEquals(windowAlignCenter, rect.left, DELTA);
5917
5918        // test 3: including high padding
5919        setSelectedPosition(2);
5920        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
5921        assertTrue(textView.getPaddingRight() > 0);
5922        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
5923                textView.getHeight());
5924        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5925        assertEquals(windowAlignCenter, rect.right, DELTA);
5926
5927        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
5928        setSelectedPosition(3);
5929        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
5930        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
5931        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
5932        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5933        assertEquals(windowAlignCenter, rect.right, DELTA);
5934
5935        // test 5: does not include padding
5936        setSelectedPosition(4);
5937        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
5938        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
5939        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
5940        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5941        assertEquals(windowAlignCenter, rect.right, DELTA);
5942    }
5943
5944    @Test
5945    public void testItemAlignmentHorizontalRtl() throws Throwable {
5946        Intent intent = new Intent();
5947        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
5948        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
5949        int[] items = new int[5];
5950        for (int i = 0; i < items.length; i++) {
5951            items[i] = 300;
5952        }
5953        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
5954        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
5955        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
5956                FiveViewTypesProvider.class.getName());
5957        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
5958                ItemAlignmentWithPaddingFacetProvider.class.getName());
5959        mOrientation = BaseGridView.VERTICAL;
5960        mNumRows = 1;
5961
5962        initActivity(intent);
5963        startWaitLayout();
5964        mActivityTestRule.runOnUiThread(new Runnable() {
5965            @Override
5966            public void run() {
5967                mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
5968                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
5969                mGridView.setWindowAlignmentOffsetPercent(50);
5970                mGridView.setWindowAlignmentOffset(0);
5971            }
5972        });
5973        waitForLayout();
5974
5975        final float windowAlignCenter = mGridView.getWidth() / 2f;
5976        Rect rect = new Rect();
5977        View textView;
5978
5979        // test 1: does not include padding
5980        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
5981        rect.set(0, 0, textView.getWidth(), textView.getHeight());
5982        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5983        assertEquals(windowAlignCenter, rect.right, DELTA);
5984
5985        // test 2: including low padding
5986        setSelectedPosition(1);
5987        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
5988        assertTrue(textView.getPaddingRight() > 0);
5989        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
5990                textView.getHeight());
5991        mGridView.offsetDescendantRectToMyCoords(textView, rect);
5992        assertEquals(windowAlignCenter, rect.right, DELTA);
5993
5994        // test 3: including high padding
5995        setSelectedPosition(2);
5996        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
5997        assertTrue(textView.getPaddingLeft() > 0);
5998        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(),
5999                textView.getHeight());
6000        mGridView.offsetDescendantRectToMyCoords(textView, rect);
6001        assertEquals(windowAlignCenter, rect.left, DELTA);
6002
6003        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
6004        setSelectedPosition(3);
6005        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
6006        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
6007        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
6008        mGridView.offsetDescendantRectToMyCoords(textView, rect);
6009        assertEquals(windowAlignCenter, rect.right, DELTA);
6010
6011        // test 5: does not include padding
6012        setSelectedPosition(4);
6013        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
6014        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
6015        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
6016        mGridView.offsetDescendantRectToMyCoords(textView, rect);
6017        assertEquals(windowAlignCenter, rect.right, DELTA);
6018    }
6019
6020    enum ItemLocation {
6021        ITEM_AT_LOW,
6022        ITEM_AT_KEY_LINE,
6023        ITEM_AT_HIGH
6024    };
6025
6026    static class ItemAt {
6027        final int mScrollPosition;
6028        final int mPosition;
6029        final ItemLocation mLocation;
6030
6031        ItemAt(int scrollPosition, int position, ItemLocation loc) {
6032            mScrollPosition = scrollPosition;
6033            mPosition = position;
6034            mLocation = loc;
6035        }
6036
6037        ItemAt(int position, ItemLocation loc) {
6038            mScrollPosition = position;
6039            mPosition = position;
6040            mLocation = loc;
6041        }
6042    }
6043
6044    /**
6045     * When scroll to position, item at position is expected at given location.
6046     */
6047    static ItemAt itemAt(int position, ItemLocation location) {
6048        return new ItemAt(position, location);
6049    }
6050
6051    /**
6052     * When scroll to scrollPosition, item at position is expected at given location.
6053     */
6054    static ItemAt itemAt(int scrollPosition, int position, ItemLocation location) {
6055        return new ItemAt(scrollPosition, position, location);
6056    }
6057
6058    void prepareKeyLineTest(int numItems) throws Throwable {
6059        Intent intent = new Intent();
6060        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
6061        int[] items = new int[numItems];
6062        for (int i = 0; i < items.length; i++) {
6063            items[i] = 32;
6064        }
6065        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
6066        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
6067        mOrientation = BaseGridView.HORIZONTAL;
6068        mNumRows = 1;
6069
6070        initActivity(intent);
6071    }
6072
6073    public void testPreferKeyLine(final int windowAlignment,
6074            final boolean preferKeyLineOverLow,
6075            final boolean preferKeyLineOverHigh,
6076            ItemLocation assertFirstItemLocation,
6077            ItemLocation assertLastItemLocation) throws Throwable {
6078        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
6079                itemAt(0, assertFirstItemLocation),
6080                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
6081    }
6082
6083    public void testPreferKeyLine(final int windowAlignment,
6084            final boolean preferKeyLineOverLow,
6085            final boolean preferKeyLineOverHigh,
6086            ItemLocation assertFirstItemLocation,
6087            ItemAt assertLastItemLocation) throws Throwable {
6088        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
6089                itemAt(0, assertFirstItemLocation),
6090                assertLastItemLocation);
6091    }
6092
6093    public void testPreferKeyLine(final int windowAlignment,
6094            final boolean preferKeyLineOverLow,
6095            final boolean preferKeyLineOverHigh,
6096            ItemAt assertFirstItemLocation,
6097            ItemLocation assertLastItemLocation) throws Throwable {
6098        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
6099                assertFirstItemLocation,
6100                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
6101    }
6102
6103    public void testPreferKeyLine(final int windowAlignment,
6104            final boolean preferKeyLineOverLow,
6105            final boolean preferKeyLineOverHigh,
6106            ItemAt assertFirstItemLocation,
6107            ItemAt assertLastItemLocation) throws Throwable {
6108        TestPreferKeyLineOptions options = new TestPreferKeyLineOptions();
6109        options.mAssertItemLocations = new ItemAt[] {assertFirstItemLocation,
6110                assertLastItemLocation};
6111        options.mPreferKeyLineOverLow = preferKeyLineOverLow;
6112        options.mPreferKeyLineOverHigh = preferKeyLineOverHigh;
6113        options.mWindowAlignment = windowAlignment;
6114
6115        options.mRtl = false;
6116        testPreferKeyLine(options);
6117
6118        options.mRtl = true;
6119        testPreferKeyLine(options);
6120    }
6121
6122    static class TestPreferKeyLineOptions {
6123        int mWindowAlignment;
6124        boolean mPreferKeyLineOverLow;
6125        boolean mPreferKeyLineOverHigh;
6126        ItemAt[] mAssertItemLocations;
6127        boolean mRtl;
6128    }
6129
6130    public void testPreferKeyLine(final TestPreferKeyLineOptions options) throws Throwable {
6131        startWaitLayout();
6132        mActivityTestRule.runOnUiThread(new Runnable() {
6133            @Override
6134            public void run() {
6135                if (options.mRtl) {
6136                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
6137                } else {
6138                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
6139                }
6140                mGridView.setWindowAlignment(options.mWindowAlignment);
6141                mGridView.setWindowAlignmentOffsetPercent(50);
6142                mGridView.setWindowAlignmentOffset(0);
6143                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(options.mPreferKeyLineOverLow);
6144                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(
6145                        options.mPreferKeyLineOverHigh);
6146            }
6147        });
6148        waitForLayout();
6149
6150        final int paddingStart = mGridView.getPaddingStart();
6151        final int paddingEnd = mGridView.getPaddingEnd();
6152        final int windowAlignCenter = mGridView.getWidth() / 2;
6153
6154        for (int i = 0; i < options.mAssertItemLocations.length; i++) {
6155            ItemAt assertItemLocation = options.mAssertItemLocations[i];
6156            setSelectedPosition(assertItemLocation.mScrollPosition);
6157            View view = mGridView.findViewHolderForAdapterPosition(assertItemLocation.mPosition)
6158                    .itemView;
6159            switch (assertItemLocation.mLocation) {
6160                case ITEM_AT_LOW:
6161                    if (options.mRtl) {
6162                        assertEquals(mGridView.getWidth() - paddingStart, view.getRight());
6163                    } else {
6164                        assertEquals(paddingStart, view.getLeft());
6165                    }
6166                    break;
6167                case ITEM_AT_HIGH:
6168                    if (options.mRtl) {
6169                        assertEquals(paddingEnd, view.getLeft());
6170                    } else {
6171                        assertEquals(mGridView.getWidth() - paddingEnd, view.getRight());
6172                    }
6173                    break;
6174                case ITEM_AT_KEY_LINE:
6175                    assertEquals(windowAlignCenter, (view.getLeft() + view.getRight()) / 2, DELTA);
6176                    break;
6177            }
6178        }
6179    }
6180
6181    @Test
6182    public void testPreferKeyLine1() throws Throwable {
6183        prepareKeyLineTest(1);
6184        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
6185                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6186        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
6187                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6188        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
6189                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6190        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
6191                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6192
6193        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
6194                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
6195        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
6196                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
6197        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
6198                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6199        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
6200                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6201
6202        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
6203                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
6204        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
6205                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6206        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
6207                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
6208        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
6209                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6210
6211        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
6212                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
6213        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
6214                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
6215        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
6216                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6217        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
6218                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6219    }
6220
6221    @Test
6222    public void testPreferKeyLine2() throws Throwable {
6223        prepareKeyLineTest(2);
6224        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
6225                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6226        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
6227                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6228        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
6229                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6230        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
6231                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6232
6233        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
6234                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
6235        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
6236                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
6237        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
6238                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
6239                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
6240        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
6241                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
6242                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
6243
6244        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
6245                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
6246                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
6247        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
6248                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
6249                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
6250        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
6251                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
6252                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
6253        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
6254                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
6255                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
6256
6257        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
6258                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
6259        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
6260                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
6261        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
6262                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
6263                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
6264        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
6265                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
6266                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
6267    }
6268
6269    @Test
6270    public void testPreferKeyLine10000() throws Throwable {
6271        prepareKeyLineTest(10000);
6272        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
6273                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6274        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
6275                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6276        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
6277                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6278        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
6279                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
6280
6281        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
6282                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
6283        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
6284                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
6285        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
6286                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
6287        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
6288                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
6289
6290        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
6291                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
6292        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
6293                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
6294        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
6295                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
6296        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
6297                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
6298
6299        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
6300                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
6301        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
6302                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
6303        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
6304                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
6305        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
6306                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
6307    }
6308}
6309