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