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