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