1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.support.v17.leanback.widget;
17
18import android.app.Instrumentation;
19import android.content.Intent;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Rect;
23import android.graphics.drawable.ColorDrawable;
24import android.os.Parcelable;
25import android.support.v17.leanback.tests.R;
26import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
27import android.support.v7.widget.RecyclerView;
28import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
29import android.test.ActivityInstrumentationTestCase2;
30import android.text.Selection;
31import android.text.Spannable;
32import android.util.SparseArray;
33import android.util.SparseIntArray;
34import android.view.KeyEvent;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.TextView;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Comparator;
42import java.util.HashMap;
43
44/**
45 * @hide from javadoc
46 */
47public class GridWidgetTest extends ActivityInstrumentationTestCase2<GridActivity> {
48
49    private static final boolean HUMAN_DELAY = false;
50    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
51
52    protected GridActivity mActivity;
53    protected Instrumentation mInstrumentation;
54    protected BaseGridView mGridView;
55    protected GridLayoutManager mLayoutManager;
56    protected int mOrientation;
57    protected int mNumRows;
58
59    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
60        public int compare(View lhs, View rhs) {
61            if (mOrientation == BaseGridView.HORIZONTAL) {
62                return lhs.getLeft() - rhs.getLeft();
63            } else {
64                return lhs.getTop() - rhs.getTop();
65            }
66        };
67    };
68
69    /**
70     * Verify margins between items on same row are same.
71     */
72    private final Runnable mVerifyLayout = new Runnable() {
73        @Override
74        public void run() {
75            verifyMargin();
76        }
77    };
78
79    public GridWidgetTest() {
80        super("android.support.v17.leanback.tests", GridActivity.class);
81    }
82
83    private void humanDelay(int delay) throws InterruptedException {
84        if (HUMAN_DELAY) Thread.sleep(delay);
85    }
86    /**
87     * Change size of the Adapter and notifyDataSetChanged.
88     */
89    private void changeArraySize(final int size) throws Throwable {
90        runTestOnUiThread(new Runnable() {
91            public void run() {
92                mActivity.changeArraySize(size);
93            }
94        });
95        Thread.sleep(500);
96    }
97
98    /**
99     * Change selected position.
100     */
101    private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable {
102        runTestOnUiThread(new Runnable() {
103            public void run() {
104                mGridView.setSelectedPosition(position, scrollExtra);
105            }
106        });
107        Thread.sleep(500);
108    }
109
110    protected void waitForScrollIdleAndItemAnimation(Runnable verify) throws Throwable {
111        waitForScrollIdle();
112        waitForItemAnimation();
113        verify.run();
114    }
115
116    protected void waitForItemAnimation() throws Throwable {
117        Thread.sleep(100);
118        while (mGridView.getItemAnimator() != null && mGridView.getItemAnimator().isRunning()) {
119            try {
120                Thread.sleep(100);
121            } catch (InterruptedException ex) {
122                break;
123            }
124        }
125    }
126
127    /**
128     * Wait for grid view stop scroll and optionally verify state of grid view.
129     */
130    protected void waitForScrollIdle(Runnable verify) throws Throwable {
131        Thread.sleep(100);
132        int total = 0;
133        while (mGridView.getLayoutManager().isSmoothScrolling() ||
134                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
135            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
136                throw new RuntimeException("waitForScrollIdle Timeout");
137            }
138            try {
139                Thread.sleep(100);
140            } catch (InterruptedException ex) {
141                break;
142            }
143            if (verify != null) {
144                runTestOnUiThread(verify);
145            }
146        }
147    }
148
149    /**
150     * Wait for grid view stop animation and optionally verify state of grid view.
151     */
152    protected void waitForTransientStateGone(Runnable verify) throws Throwable {
153        do {
154            try {
155                Thread.sleep(100);
156            } catch (InterruptedException ex) {
157                break;
158            }
159            if (verify != null) {
160                runTestOnUiThread(verify);
161            }
162        } while (mGridView.hasTransientState());
163    }
164
165    /**
166     * Wait for grid view stop scroll.
167     */
168    protected void waitForScrollIdle() throws Throwable {
169        waitForScrollIdle(null);
170    }
171
172    /**
173     * Scrolls using given key.
174     */
175    protected void scroll(int key, Runnable verify) throws Throwable {
176        do {
177            if (verify != null) {
178                runTestOnUiThread(verify);
179            }
180            sendRepeatedKeys(10, key);
181            try {
182                Thread.sleep(300);
183            } catch (InterruptedException ex) {
184                break;
185            }
186        } while (mGridView.getLayoutManager().isSmoothScrolling() ||
187                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
188    }
189
190    protected void scrollToBegin(Runnable verify) throws Throwable {
191        int key;
192        if (mOrientation == BaseGridView.HORIZONTAL) {
193            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
194                key = KeyEvent.KEYCODE_DPAD_RIGHT;
195            } else {
196                key = KeyEvent.KEYCODE_DPAD_LEFT;
197            }
198        } else {
199            key = KeyEvent.KEYCODE_DPAD_UP;
200        }
201        scroll(key, verify);
202    }
203
204    protected void scrollToEnd(Runnable verify) throws Throwable {
205        int key;
206        if (mOrientation == BaseGridView.HORIZONTAL) {
207            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
208                key = KeyEvent.KEYCODE_DPAD_LEFT;
209            } else {
210                key = KeyEvent.KEYCODE_DPAD_RIGHT;
211            }
212        } else {
213            key = KeyEvent.KEYCODE_DPAD_DOWN;
214        }
215        scroll(key, verify);
216    }
217
218    /**
219     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
220     */
221    protected View[][] sortByRows() {
222        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
223        ArrayList<Integer> rowLocations = new ArrayList();
224        for (int i = 0; i < mGridView.getChildCount(); i++) {
225            View v = mGridView.getChildAt(i);
226            int rowLocation;
227            if (mOrientation == BaseGridView.HORIZONTAL) {
228                rowLocation = v.getTop();
229            } else {
230                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL ?
231                    v.getRight() : v.getLeft();
232            }
233            ArrayList<View> views = rows.get(rowLocation);
234            if (views == null) {
235                views = new ArrayList<View>();
236                rows.put(rowLocation, views);
237                rowLocations.add(rowLocation);
238            }
239            views.add(v);
240        }
241        Object[] sortedLocations = rowLocations.toArray();
242        Arrays.sort(sortedLocations);
243        if (mNumRows != rows.size()) {
244            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
245        }
246        View[][] sorted = new View[rows.size()][];
247        for (int i = 0; i < rowLocations.size(); i++) {
248            Integer rowLocation = rowLocations.get(i);
249            ArrayList<View> arr = rows.get(rowLocation);
250            View[] views = arr.toArray(new View[arr.size()]);
251            Arrays.sort(views, mRowSortComparator);
252            sorted[i] = views;
253        }
254        return sorted;
255    }
256
257    protected void verifyMargin() {
258        View[][] sorted = sortByRows();
259        for (int row = 0; row < sorted.length; row++) {
260            View[] views = sorted[row];
261            int margin = -1;
262            for (int i = 1; i < views.length; i++) {
263                if (mOrientation == BaseGridView.HORIZONTAL) {
264                    assertEquals(mGridView.getHorizontalMargin(),
265                            views[i].getLeft() - views[i - 1].getRight());
266                } else {
267                    assertEquals(mGridView.getVerticalMargin(),
268                            views[i].getTop() - views[i - 1].getBottom());
269                }
270            }
271        }
272    }
273
274    protected void verifyBeginAligned() {
275        View[][] sorted = sortByRows();
276        int alignedLocation = 0;
277        if (mOrientation == BaseGridView.HORIZONTAL) {
278            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
279                for (int i = 0; i < sorted.length; i++) {
280                    if (i == 0) {
281                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
282                    } else {
283                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
284                    }
285                }
286            } else {
287                for (int i = 0; i < sorted.length; i++) {
288                    if (i == 0) {
289                        alignedLocation = sorted[i][0].getLeft();
290                    } else {
291                        assertEquals(alignedLocation, sorted[i][0].getLeft());
292                    }
293                }
294            }
295        } else {
296            for (int i = 0; i < sorted.length; i++) {
297                if (i == 0) {
298                    alignedLocation = sorted[i][0].getTop();
299                } else {
300                    assertEquals(alignedLocation, sorted[i][0].getTop());
301                }
302            }
303        }
304    }
305
306    protected int[] getEndEdges() {
307        View[][] sorted = sortByRows();
308        int[] edges = new int[sorted.length];
309        if (mOrientation == BaseGridView.HORIZONTAL) {
310            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
311                for (int i = 0; i < sorted.length; i++) {
312                    edges[i] = sorted[i][0].getLeft();
313                }
314            } else {
315                for (int i = 0; i < sorted.length; i++) {
316                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
317                }
318            }
319        } else {
320            for (int i = 0; i < sorted.length; i++) {
321                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
322            }
323        }
324        return edges;
325    }
326
327    protected void verifyEdgesSame(int[] edges, int[] edges2) {
328        assertEquals(edges.length, edges2.length);
329        for (int i = 0; i < edges.length; i++) {
330            assertEquals(edges[i], edges2[i]);
331        }
332    }
333
334    protected void verifyBoundCount(int count) {
335        if (mActivity.getBoundCount() != count) {
336            StringBuffer b = new StringBuffer();
337            b.append("ItemsLength: ");
338            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
339                b.append(mActivity.mItemLengths[i]).append(",");
340            }
341            assertEquals("Bound count does not match, ItemsLengths: "+ b,
342                    count, mActivity.getBoundCount());
343        }
344    }
345
346    private static int getCenterY(View v) {
347        return (v.getTop() + v.getBottom())/2;
348    }
349
350    private static int getCenterX(View v) {
351        return (v.getLeft() + v.getRight())/2;
352    }
353
354    private void initActivity(Intent intent) {
355        setActivityIntent(intent);
356        mActivity = getActivity();
357        final String testName = getName();
358        try {
359            runTestOnUiThread(new Runnable() {
360                public void run() {
361                    mActivity.setTitle(testName);
362                }
363            });
364            Thread.sleep(1000);
365        } catch (Throwable t) {
366            t.printStackTrace();
367        }
368        mGridView = mActivity.mGridView;
369    }
370
371    public void testThreeRowHorizontalBasic() throws Throwable {
372        mInstrumentation = getInstrumentation();
373        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
374        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
375        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
376        initActivity(intent);
377        mOrientation = BaseGridView.HORIZONTAL;
378        mNumRows = 3;
379
380        scrollToEnd(mVerifyLayout);
381        verifyBoundCount(100);
382
383        scrollToBegin(mVerifyLayout);
384
385        verifyBeginAligned();
386    }
387
388    static class DividerDecoration extends RecyclerView.ItemDecoration {
389
390        private ColorDrawable mTopDivider;
391        private ColorDrawable mBottomDivider;
392        private int mLeftOffset;
393        private int mRightOffset;
394        private int mTopOffset;
395        private int mBottomOffset;
396
397        DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
398            mLeftOffset = leftOffset;
399            mTopOffset = topOffset;
400            mRightOffset = rightOffset;
401            mBottomOffset = bottomOffset;
402        }
403
404        @Override
405        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
406            if (mTopDivider == null) {
407                mTopDivider = new ColorDrawable(Color.RED);
408            }
409            if (mBottomDivider == null) {
410                mBottomDivider = new ColorDrawable(Color.BLUE);
411            }
412            final int childCount = parent.getChildCount();
413            final int width = parent.getWidth();
414            for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
415                final View view = parent.getChildAt(childViewIndex);
416                mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY());
417                mTopDivider.draw(c);
418                mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width,
419                        (int) view.getY() + view.getHeight() + mBottomOffset);
420                mBottomDivider.draw(c);
421            }
422        }
423
424        @Override
425        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
426                                   RecyclerView.State state) {
427            outRect.left = mLeftOffset;
428            outRect.top = mTopOffset;
429            outRect.right = mRightOffset;
430            outRect.bottom = mBottomOffset;
431        }
432    }
433
434    public void testItemDecorationAndMargins() throws Throwable {
435
436        final int leftMargin = 3;
437        final int topMargin = 4;
438        final int rightMargin = 7;
439        final int bottomMargin = 8;
440        final int itemHeight = 100;
441
442        mInstrumentation = getInstrumentation();
443        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
444        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
445        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
446        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
447                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
448        initActivity(intent);
449        mOrientation = BaseGridView.VERTICAL;
450        mNumRows = 1;
451
452        final int paddingLeft = mGridView.getPaddingLeft();
453        final int paddingTop = mGridView.getPaddingTop();
454        final int verticalSpace = mGridView.getVerticalMargin();
455        final int decorationLeft = 17;
456        final int decorationTop = 1;
457        final int decorationRight = 19;
458        final int decorationBottom = 2;
459
460        runTestOnUiThread(new Runnable() {
461            public void run() {
462                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
463                        decorationRight, decorationBottom));
464            }
465        });
466        waitForScrollIdle();
467
468        View child0 = mGridView.getChildAt(0);
469        View child1 = mGridView.getChildAt(1);
470        View child2 = mGridView.getChildAt(2);
471
472        assertEquals(itemHeight, child0.getBottom() - child0.getTop());
473
474        // verify left margins
475        assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft());
476        assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft());
477        assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft());
478        // verify top bottom margins and decoration offset
479        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop());
480        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
481                child1.getTop() - child0.getBottom());
482        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
483                child2.getTop() - child1.getBottom());
484
485    }
486
487    public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
488        final int leftMargin = 3;
489        final int topMargin = 4;
490        final int rightMargin = 7;
491        final int bottomMargin = 8;
492        final int itemHeight = 100;
493        final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
494
495        mInstrumentation = getInstrumentation();
496        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
497        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
498        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
499        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
500        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
501                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
502        intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
503        initActivity(intent);
504        mOrientation = BaseGridView.VERTICAL;
505        mNumRows = 1;
506
507        final int paddingLeft = mGridView.getPaddingLeft();
508        final int paddingTop = mGridView.getPaddingTop();
509        final int verticalSpace = mGridView.getVerticalMargin();
510        final int decorationLeft = 17;
511        final int decorationTop = 1;
512        final int decorationRight = 19;
513        final int decorationBottom = 2;
514
515        final Rect opticalPaddings = new Rect();
516        mGridView.getContext().getDrawable(ninePatchDrawableResourceId).getPadding(opticalPaddings);
517        final int opticalInsetsLeft = opticalPaddings.left;
518        final int opticalInsetsTop = opticalPaddings.top;
519        final int opticalInsetsRight = opticalPaddings.right;
520        final int opticalInsetsBottom = opticalPaddings.bottom;
521        assertTrue(opticalInsetsLeft > 0);
522        assertTrue(opticalInsetsTop > 0);
523        assertTrue(opticalInsetsRight > 0);
524        assertTrue(opticalInsetsBottom > 0);
525
526        runTestOnUiThread(new Runnable() {
527            public void run() {
528                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
529                        decorationRight, decorationBottom));
530            }
531        });
532        waitForScrollIdle();
533
534        View child0 = mGridView.getChildAt(0);
535        View child1 = mGridView.getChildAt(1);
536        View child2 = mGridView.getChildAt(2);
537
538        assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
539                child0.getBottom() - child0.getTop());
540
541        // verify left margins decoration and optical insets
542        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
543                child0.getLeft());
544        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
545                child1.getLeft());
546        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
547                child2.getLeft());
548        // verify top bottom margins decoration offset and optical insets
549        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
550        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
551                (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
552        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
553                (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
554
555    }
556
557    public void testThreeColumnVerticalBasic() throws Throwable {
558
559        mInstrumentation = getInstrumentation();
560        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
561        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid);
562        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
563        initActivity(intent);
564        mOrientation = BaseGridView.VERTICAL;
565        mNumRows = 3;
566
567        scrollToEnd(mVerifyLayout);
568        verifyBoundCount(200);
569
570        scrollToBegin(mVerifyLayout);
571
572        verifyBeginAligned();
573    }
574
575    public void testRedundantAppendRemove() throws Throwable {
576        mInstrumentation = getInstrumentation();
577        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
578        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
579                R.layout.vertical_grid_testredundantappendremove);
580        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
581                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
582                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
583                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
584                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
585                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
586                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
587                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
588                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
589                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
590                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
591        initActivity(intent);
592        mOrientation = BaseGridView.VERTICAL;
593        mNumRows = 3;
594
595        scrollToEnd(mVerifyLayout);
596
597        verifyBoundCount(200);
598
599        scrollToBegin(mVerifyLayout);
600
601        verifyBeginAligned();
602    }
603
604    public void testRedundantAppendRemove2() throws Throwable {
605        mInstrumentation = getInstrumentation();
606        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
607        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
608                R.layout.horizontal_grid_testredundantappendremove2);
609        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
610                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
611                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
612                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
613                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
614                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
615        initActivity(intent);
616        mOrientation = BaseGridView.HORIZONTAL;
617        mNumRows = 3;
618        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
619
620        // test append without staggered result cache
621        scrollToEnd(mVerifyLayout);
622
623        verifyBoundCount(100);
624        int[] endEdges = getEndEdges();
625
626        scrollToBegin(mVerifyLayout);
627
628        verifyBeginAligned();
629
630        // now test append with staggered result cache
631        changeArraySize(3);
632        assertEquals("Staggerd cache should be kept as is when no item size change",
633                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
634
635        mActivity.resetBoundCount();
636        changeArraySize(100);
637
638        scrollToEnd(mVerifyLayout);
639        verifyBoundCount(100);
640
641        // we should get same aligned end edges
642        int[] endEdges2 = getEndEdges();
643        verifyEdgesSame(endEdges, endEdges2);
644    }
645
646    public void testItemMovedHorizontal() throws Throwable {
647
648        mInstrumentation = getInstrumentation();
649        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
650        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
651                R.layout.horizontal_grid);
652        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
653        initActivity(intent);
654        mOrientation = BaseGridView.HORIZONTAL;
655        mNumRows = 3;
656
657        mGridView.setSelectedPositionSmooth(150);
658        waitForScrollIdle(mVerifyLayout);
659        mActivity.swap(150, 152);
660        waitForTransientStateGone(null);
661
662        runTestOnUiThread(mVerifyLayout);
663
664        scrollToBegin(mVerifyLayout);
665
666        verifyBeginAligned();
667    }
668
669    public void testItemMovedVertical() throws Throwable {
670
671        mInstrumentation = getInstrumentation();
672        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
673        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
674                R.layout.vertical_grid);
675        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
676        initActivity(intent);
677        mOrientation = BaseGridView.VERTICAL;
678        mNumRows = 3;
679
680        mGridView.setSelectedPositionSmooth(150);
681        waitForScrollIdle(mVerifyLayout);
682        mActivity.swap(150, 152);
683        waitForTransientStateGone(null);
684
685        runTestOnUiThread(mVerifyLayout);
686
687        scrollToEnd(mVerifyLayout);
688        scrollToBegin(mVerifyLayout);
689
690        verifyBeginAligned();
691    }
692
693    public void testItemAddRemoveHorizontal() throws Throwable {
694
695        mInstrumentation = getInstrumentation();
696        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
697        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
698                R.layout.horizontal_grid);
699        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
700        initActivity(intent);
701        mOrientation = BaseGridView.HORIZONTAL;
702        mNumRows = 3;
703
704        scrollToEnd(mVerifyLayout);
705        int[] endEdges = getEndEdges();
706
707        mGridView.setSelectedPositionSmooth(150);
708        waitForScrollIdle(mVerifyLayout);
709        int[] removedItems = mActivity.removeItems(151, 4);
710        waitForTransientStateGone(null);
711
712        scrollToEnd(mVerifyLayout);
713        mGridView.setSelectedPositionSmooth(150);
714        waitForScrollIdle(mVerifyLayout);
715
716        mActivity.addItems(151, removedItems);
717        waitForTransientStateGone(null);
718        scrollToEnd(mVerifyLayout);
719
720        // we should get same aligned end edges
721        int[] endEdges2 = getEndEdges();
722        verifyEdgesSame(endEdges, endEdges2);
723
724        scrollToBegin(mVerifyLayout);
725        verifyBeginAligned();
726    }
727
728    public void testSetSelectedPositionDetached() throws Throwable {
729
730        mInstrumentation = getInstrumentation();
731        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
732        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
733                R.layout.horizontal_linear);
734        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
735        initActivity(intent);
736        mOrientation = BaseGridView.HORIZONTAL;
737        mNumRows = 1;
738
739        final int focusToIndex = 49;
740        final ViewGroup parent = (ViewGroup) mGridView.getParent();
741        runTestOnUiThread(new Runnable() {
742            public void run() {
743                parent.removeView(mGridView);
744            }
745        });
746        runTestOnUiThread(new Runnable() {
747            public void run() {
748                mGridView.setSelectedPositionSmooth(focusToIndex);
749            }
750        });
751        runTestOnUiThread(new Runnable() {
752            public void run() {
753                parent.addView(mGridView);
754                mGridView.requestFocus();
755            }
756        });
757        waitForTransientStateGone(null);
758        waitForScrollIdle();
759        assertEquals(mGridView.getSelectedPosition(), focusToIndex);
760        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus());
761
762        final int focusToIndex2 = 0;
763        runTestOnUiThread(new Runnable() {
764            public void run() {
765                parent.removeView(mGridView);
766            }
767        });
768        runTestOnUiThread(new Runnable() {
769            public void run() {
770                mGridView.setSelectedPosition(focusToIndex2);
771            }
772        });
773        runTestOnUiThread(new Runnable() {
774            public void run() {
775                parent.addView(mGridView);
776                mGridView.requestFocus();
777            }
778        });
779        assertEquals(mGridView.getSelectedPosition(), focusToIndex2);
780        waitForTransientStateGone(null);
781        waitForScrollIdle();
782        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus());
783    }
784
785    public void testBug22209986() throws Throwable {
786
787        mInstrumentation = getInstrumentation();
788        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
789        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
790                R.layout.horizontal_linear);
791        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
792        initActivity(intent);
793        mOrientation = BaseGridView.HORIZONTAL;
794        mNumRows = 1;
795
796        final int focusToIndex = mGridView.getChildCount() - 1;
797        runTestOnUiThread(new Runnable() {
798            public void run() {
799                mGridView.setSelectedPositionSmooth(focusToIndex);
800            }
801        });
802
803        waitForTransientStateGone(null);
804        waitForScrollIdle();
805        runTestOnUiThread(new Runnable() {
806            public void run() {
807                mGridView.setSelectedPositionSmooth(focusToIndex + 1);
808            }
809        });
810        // let the scroll running for a while and requestLayout during scroll
811        Thread.sleep(80);
812        runTestOnUiThread(new Runnable() {
813            public void run() {
814                assertEquals(mGridView.getScrollState(), BaseGridView.SCROLL_STATE_SETTLING);
815                mGridView.requestLayout();
816            }
817        });
818        waitForTransientStateGone(null);
819        waitForScrollIdle();
820
821        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
822
823        runTestOnUiThread(new Runnable() {
824            public void run() {
825                mGridView.requestLayout();
826            }
827        });
828        waitForTransientStateGone(null);
829        waitForScrollIdle();
830        assertEquals(leftEdge,
831                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
832    }
833
834    public void testScrollAndRemove() throws Throwable {
835
836        mInstrumentation = getInstrumentation();
837        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
838        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
839                R.layout.horizontal_linear);
840        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
841        initActivity(intent);
842        mOrientation = BaseGridView.HORIZONTAL;
843        mNumRows = 1;
844
845        final int focusToIndex = mGridView.getChildCount() - 1;
846        runTestOnUiThread(new Runnable() {
847            public void run() {
848                mGridView.setSelectedPositionSmooth(focusToIndex);
849            }
850        });
851
852        runTestOnUiThread(new Runnable() {
853            public void run() {
854                mActivity.removeItems(focusToIndex, 1);
855            }
856        });
857
858        waitForTransientStateGone(null);
859        waitForScrollIdle();
860        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
861
862        runTestOnUiThread(new Runnable() {
863            public void run() {
864                mGridView.requestLayout();
865            }
866        });
867        waitForTransientStateGone(null);
868        waitForScrollIdle();
869        assertEquals(leftEdge,
870                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
871    }
872
873    public void testScrollAndInsert() throws Throwable {
874
875        mInstrumentation = getInstrumentation();
876        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
877        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
878                R.layout.vertical_grid);
879        int[] items = new int[1000];
880        for (int i = 0; i < items.length; i++) {
881            items[i] = 300 + (int)(Math.random() * 100);
882        }
883        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
884        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
885        mOrientation = BaseGridView.VERTICAL;
886        mNumRows = 3;
887
888        initActivity(intent);
889
890        runTestOnUiThread(new Runnable() {
891            public void run() {
892                mGridView.setSelectedPositionSmooth(150);
893            }
894        });
895        waitForScrollIdle(mVerifyLayout);
896
897        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
898        final int focusToIndex = mGridView.getChildAdapterPosition(view);
899        runTestOnUiThread(new Runnable() {
900            public void run() {
901                mGridView.setSelectedPositionSmooth(focusToIndex);
902            }
903        });
904
905        runTestOnUiThread(new Runnable() {
906            public void run() {
907                int[] newItems = new int[]{300, 300, 300};
908                mActivity.addItems(0, newItems);
909            }
910        });
911
912        waitForTransientStateGone(null);
913        waitForScrollIdle();
914    }
915
916    public void testScrollAndInsertBeforeVisibleItem() throws Throwable {
917
918        mInstrumentation = getInstrumentation();
919        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
920        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
921                R.layout.vertical_grid);
922        int[] items = new int[1000];
923        for (int i = 0; i < items.length; i++) {
924            items[i] = 300 + (int)(Math.random() * 100);
925        }
926        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
927        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
928        mOrientation = BaseGridView.VERTICAL;
929        mNumRows = 3;
930
931        initActivity(intent);
932
933        runTestOnUiThread(new Runnable() {
934            public void run() {
935                mGridView.setSelectedPositionSmooth(150);
936            }
937        });
938        waitForScrollIdle(mVerifyLayout);
939
940        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
941        final int focusToIndex = mGridView.getChildAdapterPosition(view);
942        runTestOnUiThread(new Runnable() {
943            public void run() {
944                mGridView.setSelectedPositionSmooth(focusToIndex);
945            }
946        });
947
948        runTestOnUiThread(new Runnable() {
949            public void run() {
950                int[] newItems = new int[]{300, 300, 300};
951                mActivity.addItems(focusToIndex, newItems);
952            }
953        });
954
955        waitForTransientStateGone(null);
956        waitForScrollIdle();
957    }
958
959    public void testSmoothScrollAndRemove() throws Throwable {
960
961        mInstrumentation = getInstrumentation();
962        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
963        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
964                R.layout.horizontal_linear);
965        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
966        initActivity(intent);
967        mOrientation = BaseGridView.HORIZONTAL;
968        mNumRows = 1;
969
970        final int focusToIndex = 40;
971        runTestOnUiThread(new Runnable() {
972            public void run() {
973                mGridView.setSelectedPositionSmooth(focusToIndex);
974            }
975        });
976
977        runTestOnUiThread(new Runnable() {
978            public void run() {
979                mActivity.removeItems(focusToIndex, 1);
980            }
981        });
982
983        Thread.sleep(20); // wait for layout
984        assertTrue("removing the index of not attached child should not affect smooth scroller",
985                mGridView.getLayoutManager().isSmoothScrolling());
986        waitForTransientStateGone(null);
987        waitForScrollIdle();
988        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
989
990        runTestOnUiThread(new Runnable() {
991            public void run() {
992                mGridView.requestLayout();
993            }
994        });
995        waitForTransientStateGone(null);
996        waitForScrollIdle();
997        assertEquals(leftEdge,
998                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
999    }
1000
1001    public void testSmoothScrollAndRemove2() throws Throwable {
1002
1003        mInstrumentation = getInstrumentation();
1004        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1005        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1006                R.layout.horizontal_linear);
1007        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
1008        initActivity(intent);
1009        mOrientation = BaseGridView.HORIZONTAL;
1010        mNumRows = 1;
1011
1012        final int focusToIndex = 40;
1013        runTestOnUiThread(new Runnable() {
1014            public void run() {
1015                mGridView.setSelectedPositionSmooth(focusToIndex);
1016            }
1017        });
1018
1019        final int removeIndex = mGridView.getChildCount() - 1;
1020        runTestOnUiThread(new Runnable() {
1021            public void run() {
1022                mActivity.removeItems(removeIndex, 1);
1023            }
1024        });
1025
1026        Thread.sleep(20); // wait for layout
1027        assertFalse("removing the index of attached child should kill smooth scroller",
1028                mGridView.getLayoutManager().isSmoothScrolling());
1029        waitForTransientStateGone(null);
1030        waitForScrollIdle();
1031        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
1032
1033        runTestOnUiThread(new Runnable() {
1034            public void run() {
1035                mGridView.requestLayout();
1036            }
1037        });
1038        waitForTransientStateGone(null);
1039        waitForScrollIdle();
1040        assertEquals(leftEdge,
1041                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
1042    }
1043
1044    public void testPendingSmoothScrollAndRemove() throws Throwable {
1045        mInstrumentation = getInstrumentation();
1046        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1047        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1048                R.layout.vertical_linear);
1049        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1050        int[] items = new int[100];
1051        for (int i = 0; i < items.length; i++) {
1052            items[i] = 630 + (int)(Math.random() * 100);
1053        }
1054        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1055        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
1056        mOrientation = BaseGridView.VERTICAL;
1057        mNumRows = 1;
1058
1059        initActivity(intent);
1060
1061        mGridView.setSelectedPositionSmooth(0);
1062        waitForScrollIdle(mVerifyLayout);
1063        assertTrue(mGridView.getChildAt(0).hasFocus());
1064
1065        // Pressing lots of key to make sure smooth scroller is running
1066        for (int i = 0; i < 20; i++) {
1067            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1068        }
1069        Thread.sleep(100);
1070
1071        assertTrue(mGridView.getLayoutManager().isSmoothScrolling());
1072        final int removeIndex = mGridView.getChildCount() - 1;
1073        runTestOnUiThread(new Runnable() {
1074            public void run() {
1075                mActivity.removeItems(removeIndex, 1);
1076            }
1077        });
1078
1079        Thread.sleep(20); // wait for layout
1080        assertFalse("removing the index of attached child should kill smooth scroller",
1081                mGridView.getLayoutManager().isSmoothScrolling());
1082
1083        waitForTransientStateGone(null);
1084        waitForScrollIdle();
1085        int focusIndex = mGridView.getSelectedPosition();
1086        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getLeft();
1087
1088        runTestOnUiThread(new Runnable() {
1089            public void run() {
1090                mGridView.requestLayout();
1091            }
1092        });
1093        waitForTransientStateGone(null);
1094        waitForScrollIdle();
1095        assertEquals(leftEdge,
1096                mGridView.getLayoutManager().findViewByPosition(focusIndex).getLeft());
1097    }
1098
1099    public void testFocusToFirstItem() throws Throwable {
1100
1101        mInstrumentation = getInstrumentation();
1102        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1103        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1104                R.layout.horizontal_grid);
1105        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
1106        initActivity(intent);
1107        mOrientation = BaseGridView.HORIZONTAL;
1108        mNumRows = 3;
1109
1110        int[] removedItems = mActivity.removeItems(0, 200);
1111
1112        waitForTransientStateGone(null);
1113        humanDelay(500);
1114        mActivity.addItems(0, removedItems);
1115
1116        waitForTransientStateGone(null);
1117        humanDelay(500);
1118        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
1119
1120        changeArraySize(0);
1121
1122        changeArraySize(200);
1123        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
1124    }
1125
1126    public void testNonFocusableHorizontal() throws Throwable {
1127        final int numItems = 200;
1128        final int startPos = 45;
1129        final int skips = 20;
1130        final int numColumns = 3;
1131        final int endPos = startPos + numColumns * (skips + 1);
1132
1133        mInstrumentation = getInstrumentation();
1134        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1135        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1136                R.layout.horizontal_grid);
1137        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1138        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1139        mOrientation = BaseGridView.HORIZONTAL;
1140        mNumRows = numColumns;
1141        boolean[] focusable = new boolean[numItems];
1142        for (int i = 0; i < focusable.length; i++) {
1143            focusable[i] = true;
1144        }
1145        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
1146            focusable[i] = false;
1147        }
1148        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1149        initActivity(intent);
1150
1151        mGridView.setSelectedPositionSmooth(startPos);
1152        waitForScrollIdle(mVerifyLayout);
1153
1154        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
1155            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
1156        } else {
1157            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
1158        }
1159        waitForScrollIdle(mVerifyLayout);
1160        assertEquals(endPos, mGridView.getSelectedPosition());
1161
1162        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
1163            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
1164        } else {
1165            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
1166        }
1167        waitForScrollIdle(mVerifyLayout);
1168        assertEquals(startPos, mGridView.getSelectedPosition());
1169
1170    }
1171
1172    public void testNoInitialFocusable() throws Throwable {
1173
1174        mInstrumentation = getInstrumentation();
1175        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1176        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1177                R.layout.horizontal_linear);
1178        final int numItems = 100;
1179        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1180        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1181        mOrientation = BaseGridView.HORIZONTAL;
1182        mNumRows = 1;
1183        boolean[] focusable = new boolean[numItems];
1184        final int firstFocusableIndex = 10;
1185        for (int i = 0; i < firstFocusableIndex; i++) {
1186            focusable[i] = false;
1187        }
1188        for (int i = firstFocusableIndex; i < focusable.length; i++) {
1189            focusable[i] = true;
1190        }
1191        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1192        initActivity(intent);
1193        assertTrue(mGridView.isFocused());
1194
1195        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
1196            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
1197        } else {
1198            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
1199        }
1200        waitForScrollIdle(mVerifyLayout);
1201        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
1202        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
1203    }
1204
1205    public void testFocusOutOfEmptyListView() throws Throwable {
1206
1207        mInstrumentation = getInstrumentation();
1208        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1209        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1210                R.layout.horizontal_linear);
1211        final int numItems = 100;
1212        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1213        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1214        mOrientation = BaseGridView.HORIZONTAL;
1215        mNumRows = 1;
1216        initActivity(intent);
1217
1218        final View horizontalGridView = new HorizontalGridViewEx(mGridView.getContext());
1219        runTestOnUiThread(new Runnable() {
1220            @Override
1221            public void run() {
1222                horizontalGridView.setFocusable(true);
1223                horizontalGridView.setFocusableInTouchMode(true);
1224                horizontalGridView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
1225                ((ViewGroup) mGridView.getParent()).addView(horizontalGridView, 0);
1226                horizontalGridView.requestFocus();
1227            }
1228        });
1229
1230        assertTrue(horizontalGridView.isFocused());
1231
1232        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1233
1234        assertTrue(mGridView.hasFocus());
1235    }
1236
1237    public void testTransferFocusToChildWhenGainFocus() throws Throwable {
1238
1239        mInstrumentation = getInstrumentation();
1240        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1241        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1242                R.layout.horizontal_linear);
1243        final int numItems = 100;
1244        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1245        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1246        mOrientation = BaseGridView.HORIZONTAL;
1247        mNumRows = 1;
1248        boolean[] focusable = new boolean[numItems];
1249        final int firstFocusableIndex = 1;
1250        for (int i = 0; i < firstFocusableIndex; i++) {
1251            focusable[i] = false;
1252        }
1253        for (int i = firstFocusableIndex; i < focusable.length; i++) {
1254            focusable[i] = true;
1255        }
1256        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1257        initActivity(intent);
1258
1259        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
1260        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
1261    }
1262
1263    public void testFocusFromSecondChild() throws Throwable {
1264
1265        mInstrumentation = getInstrumentation();
1266        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1267        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1268                R.layout.horizontal_linear);
1269        final int numItems = 100;
1270        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1271        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1272        mOrientation = BaseGridView.HORIZONTAL;
1273        mNumRows = 1;
1274        boolean[] focusable = new boolean[numItems];
1275        for (int i = 0; i < focusable.length; i++) {
1276            focusable[i] = false;
1277        }
1278        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1279        initActivity(intent);
1280
1281        // switching Adapter to cause a full rebind,  test if it will focus to second item.
1282        runTestOnUiThread(new Runnable() {
1283            @Override
1284            public void run() {
1285                mActivity.mNumItems = numItems;
1286                mActivity.mItemFocusables[1] = true;
1287                mActivity.rebindToNewAdapter();
1288            }
1289        });
1290    }
1291
1292    public void testNonFocusableVertical() throws Throwable {
1293        final int numItems = 200;
1294        final int startPos = 44;
1295        final int skips = 20;
1296        final int numColumns = 3;
1297        final int endPos = startPos + numColumns * (skips + 1);
1298
1299        mInstrumentation = getInstrumentation();
1300        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1301        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1302                R.layout.vertical_grid);
1303        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1304        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1305        mOrientation = BaseGridView.VERTICAL;
1306        mNumRows = numColumns;
1307        boolean[] focusable = new boolean[numItems];
1308        for (int i = 0; i < focusable.length; i++) {
1309            focusable[i] = true;
1310        }
1311        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
1312            focusable[i] = false;
1313        }
1314        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1315        initActivity(intent);
1316
1317        mGridView.setSelectedPositionSmooth(startPos);
1318        waitForScrollIdle(mVerifyLayout);
1319
1320        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1321        waitForScrollIdle(mVerifyLayout);
1322        assertEquals(endPos, mGridView.getSelectedPosition());
1323
1324        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
1325        waitForScrollIdle(mVerifyLayout);
1326        assertEquals(startPos, mGridView.getSelectedPosition());
1327
1328    }
1329
1330    public void testLtrFocusOutStartDisabled() throws Throwable {
1331        final int numItems = 200;
1332
1333        mInstrumentation = getInstrumentation();
1334        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1335        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr);
1336        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1337        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1338        mOrientation = BaseGridView.VERTICAL;
1339        mNumRows = 1;
1340        initActivity(intent);
1341
1342        runTestOnUiThread(new Runnable() {
1343            @Override
1344            public void run() {
1345                mGridView.requestFocus();
1346                mGridView.setSelectedPositionSmooth(0);
1347            }
1348        });
1349        waitForScrollIdle(mVerifyLayout);
1350
1351        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
1352        waitForScrollIdle(mVerifyLayout);
1353        assertTrue(mGridView.hasFocus());
1354    }
1355
1356    public void testRtlFocusOutStartDisabled() throws Throwable {
1357        final int numItems = 200;
1358
1359        mInstrumentation = getInstrumentation();
1360        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1361        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
1362        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1363        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1364        mOrientation = BaseGridView.VERTICAL;
1365        mNumRows = 1;
1366        initActivity(intent);
1367
1368        runTestOnUiThread(new Runnable() {
1369            @Override
1370            public void run() {
1371                mGridView.requestFocus();
1372                mGridView.setSelectedPositionSmooth(0);
1373            }
1374        });
1375        waitForScrollIdle(mVerifyLayout);
1376
1377        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
1378        waitForScrollIdle(mVerifyLayout);
1379        assertTrue(mGridView.hasFocus());
1380    }
1381
1382    public void testTransferFocusable() throws Throwable {
1383        final int numItems = 200;
1384        final int numColumns = 3;
1385        final int startPos = 1;
1386
1387        mInstrumentation = getInstrumentation();
1388        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1389        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1390                R.layout.horizontal_grid);
1391        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1392        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1393        mOrientation = BaseGridView.HORIZONTAL;
1394        mNumRows = numColumns;
1395        boolean[] focusable = new boolean[numItems];
1396        for (int i = 0; i < focusable.length; i++) {
1397            focusable[i] = true;
1398        }
1399        for (int i = 0; i < startPos; i++) {
1400            focusable[i] = false;
1401        }
1402        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1403        initActivity(intent);
1404
1405        changeArraySize(0);
1406        assertTrue(mGridView.isFocused());
1407
1408        changeArraySize(numItems);
1409        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
1410    }
1411
1412    public void testTransferFocusable2() throws Throwable {
1413        final int numItems = 200;
1414        final int numColumns = 3;
1415        final int startPos = 10;
1416
1417        mInstrumentation = getInstrumentation();
1418        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1419        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1420                R.layout.horizontal_grid);
1421        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
1422        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
1423        mOrientation = BaseGridView.HORIZONTAL;
1424        mNumRows = numColumns;
1425        boolean[] focusable = new boolean[numItems];
1426        for (int i = 0; i < focusable.length; i++) {
1427            focusable[i] = true;
1428        }
1429        for (int i = 0; i < startPos; i++) {
1430            focusable[i] = false;
1431        }
1432        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
1433        initActivity(intent);
1434
1435        changeArraySize(0);
1436        assertTrue(mGridView.isFocused());
1437
1438        changeArraySize(numItems);
1439        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
1440    }
1441
1442    public void testNonFocusableLoseInFastLayout() throws Throwable {
1443        mInstrumentation = getInstrumentation();
1444        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1445        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1446                R.layout.vertical_linear);
1447        int[] items = new int[300];
1448        for (int i = 0; i < items.length; i++) {
1449            items[i] = 480;
1450        }
1451        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1452        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1453        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
1454        mOrientation = BaseGridView.VERTICAL;
1455        mNumRows = 1;
1456        int pressDown = 15;
1457
1458        initActivity(intent);
1459
1460        mGridView.setSelectedPositionSmooth(0);
1461        waitForScrollIdleAndItemAnimation(mVerifyLayout);
1462
1463        for (int i = 0; i < pressDown; i++) {
1464            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1465        }
1466        waitForScrollIdleAndItemAnimation(mVerifyLayout);
1467        assertFalse(mGridView.isFocused());
1468
1469    }
1470
1471    public void testSetSelectionWithDelta() throws Throwable {
1472        mInstrumentation = getInstrumentation();
1473        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1474        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1475                R.layout.vertical_linear);
1476        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
1477        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1478        mOrientation = BaseGridView.VERTICAL;
1479        mNumRows = 1;
1480
1481        initActivity(intent);
1482
1483        runTestOnUiThread(new Runnable() {
1484            public void run() {
1485                mGridView.setSelectedPositionSmooth(3);
1486            }
1487        });
1488        waitForScrollIdle(mVerifyLayout);
1489        int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
1490
1491        humanDelay(1000);
1492
1493        // scroll to position with delta
1494        setSelectedPosition(3, 100);
1495        int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
1496        assertEquals(top1 - 100, top2);
1497
1498        // scroll to same position without delta, it will be reset
1499        setSelectedPosition(3, 0);
1500        int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
1501        assertEquals(top1, top3);
1502
1503        // scroll invisible item after last visible item
1504        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1505                .mGrid.getLastVisibleIndex();
1506        setSelectedPosition(lastVisiblePos + 1, 100);
1507        int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop();
1508        assertEquals(top1 - 100, top4);
1509
1510        // scroll invisible item before first visible item
1511        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1512                .mGrid.getFirstVisibleIndex();
1513        setSelectedPosition(firstVisiblePos - 1, 100);
1514        int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop();
1515        assertEquals(top1 - 100, top5);
1516
1517        // scroll to invisible item that is far away.
1518        setSelectedPosition(50, 100);
1519        int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop();
1520        assertEquals(top1 - 100, top6);
1521
1522        // scroll to invisible item that is far away.
1523        runTestOnUiThread(new Runnable() {
1524            public void run() {
1525                mGridView.setSelectedPositionSmooth(100);
1526            }
1527        });
1528        waitForScrollIdle(mVerifyLayout);
1529        int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop();
1530        assertEquals(top1, top7);
1531
1532        // scroll to invisible item that is far away.
1533        setSelectedPosition(10, 50);
1534        int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop();
1535        assertEquals(top1 - 50, top8);
1536    }
1537
1538    public void testSetSelectionWithDeltaInGrid() throws Throwable {
1539        mInstrumentation = getInstrumentation();
1540        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1541        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1542                R.layout.vertical_grid);
1543        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
1544        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
1545        mOrientation = BaseGridView.VERTICAL;
1546        mNumRows = 3;
1547
1548        initActivity(intent);
1549
1550        runTestOnUiThread(new Runnable() {
1551            public void run() {
1552                mGridView.setSelectedPositionSmooth(10);
1553            }
1554        });
1555        waitForScrollIdle(mVerifyLayout);
1556        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
1557
1558        humanDelay(500);
1559
1560        // scroll to position with delta
1561        setSelectedPosition(20, 100);
1562        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
1563        assertEquals(top1 - 100, top2);
1564
1565        // scroll to same position without delta, it will be reset
1566        setSelectedPosition(20, 0);
1567        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
1568        assertEquals(top1, top3);
1569
1570        // scroll invisible item after last visible item
1571        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1572                .mGrid.getLastVisibleIndex();
1573        setSelectedPosition(lastVisiblePos + 1, 100);
1574        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
1575        verifyMargin();
1576        assertEquals(top1 - 100, top4);
1577
1578        // scroll invisible item before first visible item
1579        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1580                .mGrid.getFirstVisibleIndex();
1581        setSelectedPosition(firstVisiblePos - 1, 100);
1582        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
1583        assertEquals(top1 - 100, top5);
1584
1585        // scroll to invisible item that is far away.
1586        setSelectedPosition(100, 100);
1587        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
1588        assertEquals(top1 - 100, top6);
1589
1590        // scroll to invisible item that is far away.
1591        runTestOnUiThread(new Runnable() {
1592            public void run() {
1593                mGridView.setSelectedPositionSmooth(200);
1594            }
1595        });
1596        waitForScrollIdle(mVerifyLayout);
1597        Thread.sleep(500);
1598        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
1599        assertEquals(top1, top7);
1600
1601        // scroll to invisible item that is far away.
1602        setSelectedPosition(10, 50);
1603        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
1604        assertEquals(top1 - 50, top8);
1605    }
1606
1607
1608    public void testSetSelectionWithDeltaInGrid1() throws Throwable {
1609        mInstrumentation = getInstrumentation();
1610        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1611        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1612                R.layout.vertical_grid);
1613        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
1614                193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137,
1615                188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205,
1616                223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222,
1617                150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236,
1618                123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128,
1619                192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218,
1620                192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191,
1621                212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141,
1622                156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191,
1623                132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184,
1624                164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226,
1625                132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226,
1626                127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169,
1627                182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220,
1628                178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125,
1629                214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122,
1630                188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191,
1631                199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210,
1632                164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130,
1633                137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227,
1634                193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154,
1635                192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124,
1636                172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174,
1637                146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225,
1638                174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162,
1639                192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221,
1640                120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190,
1641                204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125,
1642                202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123,
1643                237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162,
1644                139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199,
1645                215,213,122,197
1646        });
1647        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
1648        mOrientation = BaseGridView.VERTICAL;
1649        mNumRows = 3;
1650
1651        initActivity(intent);
1652
1653        runTestOnUiThread(new Runnable() {
1654            public void run() {
1655                mGridView.setSelectedPositionSmooth(10);
1656            }
1657        });
1658        waitForScrollIdle(mVerifyLayout);
1659        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
1660
1661        humanDelay(500);
1662
1663        // scroll to position with delta
1664        setSelectedPosition(20, 100);
1665        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
1666        assertEquals(top1 - 100, top2);
1667
1668        // scroll to same position without delta, it will be reset
1669        setSelectedPosition(20, 0);
1670        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
1671        assertEquals(top1, top3);
1672
1673        // scroll invisible item after last visible item
1674        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1675                .mGrid.getLastVisibleIndex();
1676        setSelectedPosition(lastVisiblePos + 1, 100);
1677        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
1678        verifyMargin();
1679        assertEquals(top1 - 100, top4);
1680
1681        // scroll invisible item before first visible item
1682        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
1683                .mGrid.getFirstVisibleIndex();
1684        setSelectedPosition(firstVisiblePos - 1, 100);
1685        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
1686        assertEquals(top1 - 100, top5);
1687
1688        // scroll to invisible item that is far away.
1689        setSelectedPosition(100, 100);
1690        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
1691        assertEquals(top1 - 100, top6);
1692
1693        // scroll to invisible item that is far away.
1694        runTestOnUiThread(new Runnable() {
1695            public void run() {
1696                mGridView.setSelectedPositionSmooth(200);
1697            }
1698        });
1699        waitForScrollIdle(mVerifyLayout);
1700        Thread.sleep(500);
1701        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
1702        assertEquals(top1, top7);
1703
1704        // scroll to invisible item that is far away.
1705        setSelectedPosition(10, 50);
1706        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
1707        assertEquals(top1 - 50, top8);
1708    }
1709
1710    public void testSmoothScrollSelectionEvents() throws Throwable {
1711        mInstrumentation = getInstrumentation();
1712        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1713        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1714                R.layout.vertical_grid);
1715        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
1716        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1717        mOrientation = BaseGridView.VERTICAL;
1718        mNumRows = 3;
1719        initActivity(intent);
1720
1721        runTestOnUiThread(new Runnable() {
1722            public void run() {
1723                mGridView.setSelectedPositionSmooth(30);
1724            }
1725        });
1726        waitForScrollIdle(mVerifyLayout);
1727        humanDelay(500);
1728
1729        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
1730        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
1731            @Override
1732            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
1733                selectedPositions.add(position);
1734            }
1735        });
1736
1737        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
1738        humanDelay(500);
1739        waitForScrollIdle(mVerifyLayout);
1740        // should only get childselected event for item 0 once
1741        assertTrue(selectedPositions.size() > 0);
1742        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
1743        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
1744            assertFalse(0 == selectedPositions.get(i).intValue());
1745        }
1746
1747    }
1748
1749    public void testSmoothScrollSelectionEventsLinear() throws Throwable {
1750        mInstrumentation = getInstrumentation();
1751        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1752        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1753                R.layout.vertical_linear);
1754        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
1755        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1756        mOrientation = BaseGridView.VERTICAL;
1757        mNumRows = 1;
1758        initActivity(intent);
1759
1760        runTestOnUiThread(new Runnable() {
1761            public void run() {
1762                mGridView.setSelectedPositionSmooth(10);
1763            }
1764        });
1765        waitForScrollIdle(mVerifyLayout);
1766        humanDelay(500);
1767
1768        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
1769        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
1770            @Override
1771            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
1772                selectedPositions.add(position);
1773            }
1774        });
1775
1776        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
1777        humanDelay(500);
1778        waitForScrollIdle(mVerifyLayout);
1779        // should only get childselected event for item 0 once
1780        assertTrue(selectedPositions.size() > 0);
1781        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
1782        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
1783            assertFalse(0 == selectedPositions.get(i).intValue());
1784        }
1785
1786    }
1787
1788    public void testScrollToNoneExisting() throws Throwable {
1789        mInstrumentation = getInstrumentation();
1790        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1791        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1792                R.layout.vertical_grid);
1793        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
1794        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1795        mOrientation = BaseGridView.VERTICAL;
1796        mNumRows = 3;
1797        initActivity(intent);
1798
1799        runTestOnUiThread(new Runnable() {
1800            public void run() {
1801                mGridView.setSelectedPositionSmooth(99);
1802            }
1803        });
1804        waitForScrollIdle(mVerifyLayout);
1805        humanDelay(500);
1806
1807
1808        runTestOnUiThread(new Runnable() {
1809            public void run() {
1810                mGridView.setSelectedPositionSmooth(50);
1811            }
1812        });
1813        Thread.sleep(100);
1814        runTestOnUiThread(new Runnable() {
1815            public void run() {
1816                mGridView.requestLayout();
1817                mGridView.setSelectedPositionSmooth(0);
1818            }
1819        });
1820        waitForScrollIdle(mVerifyLayout);
1821        humanDelay(500);
1822
1823    }
1824
1825    public void testSmoothscrollerInterrupted() throws Throwable {
1826        mInstrumentation = getInstrumentation();
1827        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1828        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1829                R.layout.vertical_linear);
1830        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1831        int[] items = new int[100];
1832        for (int i = 0; i < items.length; i++) {
1833            items[i] = 680;
1834        }
1835        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1836        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1837        mOrientation = BaseGridView.VERTICAL;
1838        mNumRows = 1;
1839
1840        initActivity(intent);
1841
1842        mGridView.setSelectedPositionSmooth(0);
1843        waitForScrollIdle(mVerifyLayout);
1844        assertTrue(mGridView.getChildAt(0).hasFocus());
1845
1846        // Pressing lots of key to make sure smooth scroller is running
1847        for (int i = 0; i < 20; i++) {
1848            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1849        }
1850        Thread.sleep(100);
1851        int total = 0;
1852        while (mGridView.getLayoutManager().isSmoothScrolling() ||
1853                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
1854            if ((total += 10) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
1855                throw new RuntimeException("waitForScrollIdle Timeout");
1856            }
1857            try {
1858                // Repeatedly pressing to make sure pending keys does not drop to zero.
1859                Thread.sleep(10);
1860                sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1861            } catch (InterruptedException ex) {
1862                break;
1863            }
1864        }
1865
1866        assertTrue("LinearSmoothScroller would not use many RV.smoothScrollBy() calls",
1867                ((VerticalGridViewEx) mGridView).mSmoothScrollByCalled < 10);
1868    }
1869
1870    public void testSmoothscrollerCancelled() throws Throwable {
1871        mInstrumentation = getInstrumentation();
1872        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1873        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1874                R.layout.vertical_linear);
1875        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1876        int[] items = new int[100];
1877        for (int i = 0; i < items.length; i++) {
1878            items[i] = 680;
1879        }
1880        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1881        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1882        mOrientation = BaseGridView.VERTICAL;
1883        mNumRows = 1;
1884
1885        initActivity(intent);
1886
1887        mGridView.setSelectedPositionSmooth(0);
1888        waitForScrollIdle(mVerifyLayout);
1889        assertTrue(mGridView.getChildAt(0).hasFocus());
1890
1891        int targetPosition = items.length - 1;
1892        mGridView.setSelectedPositionSmooth(targetPosition);
1893        runTestOnUiThread(new Runnable() {
1894            public void run() {
1895                mGridView.stopScroll();
1896            }
1897        });
1898        Thread.sleep(100);
1899        assertEquals(mGridView.getSelectedPosition(), targetPosition);
1900        assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
1901                mGridView.findFocus());
1902    }
1903
1904    public void testSetNumRowsAndAddItem() throws Throwable {
1905        mInstrumentation = getInstrumentation();
1906        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1907        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1908                R.layout.vertical_linear);
1909        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1910        int[] items = new int[2];
1911        for (int i = 0; i < items.length; i++) {
1912            items[i] = 300;
1913        }
1914        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1915        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1916        mOrientation = BaseGridView.VERTICAL;
1917        mNumRows = 1;
1918
1919        initActivity(intent);
1920
1921        mGridView.setSelectedPositionSmooth(0);
1922        waitForScrollIdle(mVerifyLayout);
1923
1924        mActivity.addItems(items.length, new int[]{300});
1925
1926        runTestOnUiThread(new Runnable() {
1927            public void run() {
1928                ((VerticalGridView) mGridView).setNumColumns(2);
1929            }
1930        });
1931        Thread.sleep(1000);
1932        assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft());
1933    }
1934
1935
1936    public void testRequestLayoutBugInLayout() throws Throwable {
1937        mInstrumentation = getInstrumentation();
1938        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1939        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1940                R.layout.vertical_linear);
1941        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
1942        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1943        int[] items = new int[100];
1944        for (int i = 0; i < items.length; i++) {
1945            items[i] = 300;
1946        }
1947        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1948        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1949        mOrientation = BaseGridView.VERTICAL;
1950        mNumRows = 1;
1951
1952        initActivity(intent);
1953
1954        runTestOnUiThread(new Runnable() {
1955            public void run() {
1956                mGridView.setSelectedPositionSmooth(1);
1957            }
1958        });
1959        waitForScrollIdle(mVerifyLayout);
1960
1961        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
1962        waitForScrollIdle(mVerifyLayout);
1963
1964        assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString());
1965    }
1966
1967
1968    public void testChangeLayoutInChild() throws Throwable {
1969        mInstrumentation = getInstrumentation();
1970        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1971        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1972                R.layout.vertical_linear_wrap_content);
1973        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
1974        int[] items = new int[2];
1975        for (int i = 0; i < items.length; i++) {
1976            items[i] = 300;
1977        }
1978        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1979        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1980        mOrientation = BaseGridView.VERTICAL;
1981        mNumRows = 1;
1982
1983        initActivity(intent);
1984
1985        runTestOnUiThread(new Runnable() {
1986            public void run() {
1987                mGridView.setSelectedPositionSmooth(0);
1988            }
1989        });
1990        waitForScrollIdleAndItemAnimation(mVerifyLayout);
1991        verifyMargin();
1992
1993        runTestOnUiThread(new Runnable() {
1994            public void run() {
1995                mGridView.setSelectedPositionSmooth(1);
1996            }
1997        });
1998        waitForScrollIdleAndItemAnimation(mVerifyLayout);
1999        verifyMargin();
2000    }
2001
2002    public void testWrapContent() throws Throwable {
2003        mInstrumentation = getInstrumentation();
2004        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2005        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2006                R.layout.horizontal_grid_wrap);
2007        int[] items = new int[200];
2008        for (int i = 0; i < items.length; i++) {
2009            items[i] = 300;
2010        }
2011        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2012        mOrientation = BaseGridView.HORIZONTAL;
2013        mNumRows = 1;
2014
2015        initActivity(intent);
2016
2017        runTestOnUiThread(new Runnable() {
2018            public void run() {
2019                mActivity.attachToNewAdapter(new int[0]);
2020            }
2021        });
2022
2023    }
2024
2025
2026    public void testZeroFixedSecondarySize() throws Throwable {
2027        mInstrumentation = getInstrumentation();
2028        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2029        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2030                R.layout.vertical_linear_measured_with_zero);
2031        intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true);
2032        int[] items = new int[2];
2033        for (int i = 0; i < items.length; i++) {
2034            items[i] = 0;
2035        }
2036        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2037        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2038        mOrientation = BaseGridView.VERTICAL;
2039        mNumRows = 1;
2040
2041        initActivity(intent);
2042
2043    }
2044
2045    public void testChildStates() throws Throwable {
2046        mInstrumentation = getInstrumentation();
2047        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2048        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
2049        int[] items = new int[100];
2050        for (int i = 0; i < items.length; i++) {
2051            items[i] = 200;
2052        }
2053        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2054        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2055        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
2056        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
2057        mOrientation = BaseGridView.VERTICAL;
2058        mNumRows = 1;
2059
2060        initActivity(intent);
2061        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
2062
2063        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
2064
2065        // 1 Save view states
2066        runTestOnUiThread(new Runnable() {
2067            public void run() {
2068                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
2069                        .getText()), 0, 1);
2070                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
2071                        .getText()), 0, 1);
2072                mGridView.saveHierarchyState(container);
2073            }
2074        });
2075
2076        // 2 Change view states
2077        runTestOnUiThread(new Runnable() {
2078            public void run() {
2079                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
2080                        .getText()), 1, 2);
2081                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
2082                        .getText()), 1, 2);
2083            }
2084        });
2085
2086        // 3 Detached and re-attached,  should still maintain state of (2)
2087        runTestOnUiThread(new Runnable() {
2088            public void run() {
2089                mGridView.setSelectedPositionSmooth(1);
2090            }
2091        });
2092        waitForScrollIdleAndItemAnimation(mVerifyLayout);
2093        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
2094        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
2095        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
2096        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
2097
2098        // 4 Recycled and rebound, should load state from (2)
2099        runTestOnUiThread(new Runnable() {
2100            public void run() {
2101                mGridView.setSelectedPositionSmooth(20);
2102            }
2103        });
2104        waitForScrollIdle(mVerifyLayout);
2105        runTestOnUiThread(new Runnable() {
2106            public void run() {
2107                mGridView.setSelectedPositionSmooth(0);
2108            }
2109        });
2110        waitForScrollIdleAndItemAnimation(mVerifyLayout);
2111        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
2112        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
2113        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
2114        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
2115    }
2116
2117
2118    public void testNoDispatchSaveChildState() throws Throwable {
2119        mInstrumentation = getInstrumentation();
2120        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2121        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
2122        int[] items = new int[100];
2123        for (int i = 0; i < items.length; i++) {
2124            items[i] = 200;
2125        }
2126        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2127        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2128        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
2129        mOrientation = BaseGridView.VERTICAL;
2130        mNumRows = 1;
2131
2132        initActivity(intent);
2133        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD);
2134
2135        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
2136
2137        // 1. Set text selection, save view states should do nothing on child
2138        runTestOnUiThread(new Runnable() {
2139            public void run() {
2140                for (int i = 0; i < mGridView.getChildCount(); i++) {
2141                    Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i))
2142                            .getText()), 0, 1);
2143                }
2144                mGridView.saveHierarchyState(container);
2145            }
2146        });
2147
2148        // 2. clear the text selection
2149        runTestOnUiThread(new Runnable() {
2150            public void run() {
2151                for (int i = 0; i < mGridView.getChildCount(); i++) {
2152                    Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i))
2153                            .getText()));
2154                }
2155            }
2156        });
2157
2158        // 3. Restore view states should be a no-op for child
2159        runTestOnUiThread(new Runnable() {
2160            public void run() {
2161                mGridView.restoreHierarchyState(container);
2162                for (int i = 0; i < mGridView.getChildCount(); i++) {
2163                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart());
2164                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd());
2165                }
2166            }
2167        });
2168    }
2169
2170
2171    static interface ViewTypeProvider {
2172        public int getViewType(int position);
2173    }
2174
2175    static interface ItemAlignmentFacetProvider {
2176        public ItemAlignmentFacet getItemAlignmentFacet(int viewType);
2177    }
2178
2179    static class TwoViewTypesProvider implements ViewTypeProvider {
2180        static int VIEW_TYPE_FIRST = 1;
2181        static int VIEW_TYPE_DEFAULT = 0;
2182        @Override
2183        public int getViewType(int position) {
2184            if (position == 0) {
2185                return VIEW_TYPE_FIRST;
2186            } else {
2187                return VIEW_TYPE_DEFAULT;
2188            }
2189        }
2190    }
2191
2192    static class ChangeableViewTypesProvider implements ViewTypeProvider {
2193        static SparseIntArray sViewTypes = new SparseIntArray();
2194        @Override
2195        public int getViewType(int position) {
2196            return sViewTypes.get(position);
2197        }
2198        public static void clear() {
2199            sViewTypes.clear();
2200        }
2201        public static void setViewType(int position, int type) {
2202            sViewTypes.put(position, type);
2203        }
2204    }
2205
2206    static class PositionItemAlignmentFacetProviderForRelativeLayout1
2207            implements ItemAlignmentFacetProvider {
2208        ItemAlignmentFacet mMultipleFacet;
2209
2210        PositionItemAlignmentFacetProviderForRelativeLayout1() {
2211            mMultipleFacet = new ItemAlignmentFacet();
2212            ItemAlignmentFacet.ItemAlignmentDef[] defs =
2213                    new ItemAlignmentFacet.ItemAlignmentDef[2];
2214            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
2215            defs[0].setItemAlignmentViewId(R.id.t1);
2216            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
2217            defs[1].setItemAlignmentViewId(R.id.t2);
2218            defs[1].setItemAlignmentOffsetPercent(100);
2219            defs[1].setItemAlignmentOffset(-10);
2220            mMultipleFacet.setAlignmentDefs(defs);
2221        }
2222
2223        @Override
2224        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
2225            if (position == 0) {
2226                return mMultipleFacet;
2227            } else {
2228                return null;
2229            }
2230        }
2231    }
2232
2233    public void testMultipleScrollPosition1() throws Throwable {
2234        mInstrumentation = getInstrumentation();
2235        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2236        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2237                R.layout.vertical_linear);
2238        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
2239        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
2240        int[] items = new int[100];
2241        for (int i = 0; i < items.length; i++) {
2242            items[i] = 300;
2243        }
2244        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2245        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2246        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
2247                TwoViewTypesProvider.class.getName());
2248        // Set ItemAlignment for each ViewHolder and view type,  ViewHolder should
2249        // override the view type settings.
2250        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
2251                PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName());
2252        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
2253                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
2254        mOrientation = BaseGridView.VERTICAL;
2255        mNumRows = 1;
2256
2257        initActivity(intent);
2258
2259        assertEquals("First view is aligned with padding top",
2260                mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop());
2261
2262        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2263        waitForScrollIdle(mVerifyLayout);
2264
2265        final View v = mGridView.getChildAt(0);
2266        View t1 = v.findViewById(R.id.t1);
2267        int t1align = (t1.getTop() + t1.getBottom()) / 2;
2268        View t2 = v.findViewById(R.id.t2);
2269        int t2align = t2.getBottom() - 10;
2270        assertEquals("Expected alignment for 2nd textview",
2271                mGridView.getPaddingTop() - (t2align - t1align),
2272                v.getTop());
2273    }
2274
2275    static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements
2276            ItemAlignmentFacetProvider {
2277        ItemAlignmentFacet mMultipleFacet;
2278
2279        PositionItemAlignmentFacetProviderForRelativeLayout2() {
2280            mMultipleFacet = new ItemAlignmentFacet();
2281            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
2282            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
2283            defs[0].setItemAlignmentViewId(R.id.t1);
2284            defs[0].setItemAlignmentOffsetPercent(0);
2285            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
2286            defs[1].setItemAlignmentViewId(R.id.t2);
2287            defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
2288            defs[1].setItemAlignmentOffset(-10);
2289            mMultipleFacet.setAlignmentDefs(defs);
2290        }
2291
2292        @Override
2293        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
2294            if (position == 0) {
2295                return mMultipleFacet;
2296            } else {
2297                return null;
2298            }
2299        }
2300    }
2301
2302    public void testMultipleScrollPosition2() throws Throwable {
2303        mInstrumentation = getInstrumentation();
2304        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2305        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
2306        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
2307        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
2308        int[] items = new int[100];
2309        for (int i = 0; i < items.length; i++) {
2310            items[i] = 300;
2311        }
2312        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2313        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2314        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
2315                TwoViewTypesProvider.class.getName());
2316        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
2317                PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
2318        mOrientation = BaseGridView.VERTICAL;
2319        mNumRows = 1;
2320
2321        initActivity(intent);
2322
2323        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
2324                mGridView.getChildAt(0).getTop());
2325
2326        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2327        waitForScrollIdle(mVerifyLayout);
2328
2329        final View v = mGridView.getChildAt(0);
2330        View t1 = v.findViewById(R.id.t1);
2331        int t1align = t1.getTop();
2332        View t2 = v.findViewById(R.id.t2);
2333        int t2align = t2.getTop() - 10;
2334        assertEquals("Expected alignment for 2nd textview",
2335                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
2336    }
2337
2338    static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements
2339            ItemAlignmentFacetProvider {
2340        ItemAlignmentFacet mMultipleFacet;
2341
2342        ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() {
2343            mMultipleFacet = new ItemAlignmentFacet();
2344            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
2345            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
2346            defs[0].setItemAlignmentViewId(R.id.t1);
2347            defs[0].setItemAlignmentOffsetPercent(0);
2348            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
2349            defs[1].setItemAlignmentViewId(R.id.t2);
2350            defs[1].setItemAlignmentOffsetPercent(100);
2351            defs[1].setItemAlignmentOffset(-10);
2352            mMultipleFacet.setAlignmentDefs(defs);
2353        }
2354
2355        @Override
2356        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
2357            if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) {
2358                return mMultipleFacet;
2359            } else {
2360                return null;
2361            }
2362        }
2363    }
2364
2365    public void testMultipleScrollPosition3() throws Throwable {
2366        mInstrumentation = getInstrumentation();
2367        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2368        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
2369        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
2370        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
2371        int[] items = new int[100];
2372        for (int i = 0; i < items.length; i++) {
2373            items[i] = 300;
2374        }
2375        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
2376        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2377        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
2378                TwoViewTypesProvider.class.getName());
2379        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
2380                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
2381        mOrientation = BaseGridView.VERTICAL;
2382        mNumRows = 1;
2383
2384        initActivity(intent);
2385
2386        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
2387                mGridView.getChildAt(0).getTop());
2388
2389        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2390        waitForScrollIdle(mVerifyLayout);
2391
2392        final View v = mGridView.getChildAt(0);
2393        View t1 = v.findViewById(R.id.t1);
2394        int t1align = t1.getTop();
2395        View t2 = v.findViewById(R.id.t2);
2396        int t2align = t2.getBottom() - 10;
2397        assertEquals("Expected alignment for 2nd textview",
2398                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
2399    }
2400
2401    public void testSelectionAndAddItemInOneCycle() throws Throwable {
2402        mInstrumentation = getInstrumentation();
2403        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2404        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2405                R.layout.vertical_linear);
2406        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
2407        initActivity(intent);
2408        mOrientation = BaseGridView.HORIZONTAL;
2409        mNumRows = 1;
2410
2411        runTestOnUiThread(new Runnable() {
2412            public void run() {
2413                mActivity.addItems(0, new int[]{300, 300});
2414                mGridView.setSelectedPosition(0);
2415            }
2416        });
2417        waitForTransientStateGone(null);
2418        assertEquals(0, mGridView.getSelectedPosition());
2419    }
2420
2421    public void testNotifyItemTypeChangedSelectionEvent() throws Throwable {
2422        mInstrumentation = getInstrumentation();
2423        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2424        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2425                R.layout.vertical_linear);
2426        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
2427        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
2428                ChangeableViewTypesProvider.class.getName());
2429        ChangeableViewTypesProvider.clear();
2430        initActivity(intent);
2431        mOrientation = BaseGridView.HORIZONTAL;
2432        mNumRows = 1;
2433
2434        final ArrayList<Integer> selectedLog = new ArrayList<Integer>();
2435        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
2436            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
2437                selectedLog.add(position);
2438            }
2439        });
2440
2441        runTestOnUiThread(new Runnable() {
2442            public void run() {
2443                ChangeableViewTypesProvider.setViewType(0, 1);
2444                mGridView.getAdapter().notifyItemChanged(0, 1);
2445            }
2446        });
2447        waitForTransientStateGone(null);
2448        assertEquals(0, mGridView.getSelectedPosition());
2449        assertEquals(selectedLog.size(), 1);
2450        assertEquals((int) selectedLog.get(0), 0);
2451    }
2452
2453    public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable {
2454        mInstrumentation = getInstrumentation();
2455        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2456        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2457                R.layout.vertical_linear);
2458        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
2459        initActivity(intent);
2460        mOrientation = BaseGridView.HORIZONTAL;
2461        mNumRows = 1;
2462
2463        runTestOnUiThread(new Runnable() {
2464            public void run() {
2465                mActivity.addItems(0, new int[]{300, 300});
2466                mGridView.setSelectedPositionSmooth(0);
2467            }
2468        });
2469        waitForTransientStateGone(null);
2470        assertEquals(0, mGridView.getSelectedPosition());
2471    }
2472
2473    public void testExtraLayoutSpace() throws Throwable {
2474        mInstrumentation = getInstrumentation();
2475        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2476        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2477                R.layout.vertical_linear);
2478        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
2479        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2480        initActivity(intent);
2481
2482        final int windowSize = mGridView.getHeight();
2483        final int extraLayoutSize = windowSize;
2484        int itemLength = mActivity.mItemLengths[0];
2485        mOrientation = BaseGridView.VERTICAL;
2486        mNumRows = 1;
2487
2488        // add extra layout space
2489        runTestOnUiThread(new Runnable() {
2490            public void run() {
2491                mGridView.setExtraLayoutSpace(extraLayoutSize);
2492            }
2493        });
2494        Thread.sleep(50);
2495        View v;
2496        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
2497        assertTrue(v.getTop() < windowSize + extraLayoutSize);
2498        assertTrue(v.getBottom() >= windowSize + extraLayoutSize -
2499                mGridView.getVerticalMargin());
2500
2501        mGridView.setSelectedPositionSmooth(150);
2502        waitForScrollIdle(mVerifyLayout);
2503        v = mGridView.getChildAt(0);
2504        assertTrue(v.getBottom() > - extraLayoutSize);
2505        assertTrue(v.getTop() <= -extraLayoutSize + mGridView.getVerticalMargin());
2506
2507        // clear extra layout space
2508        runTestOnUiThread(new Runnable() {
2509            public void run() {
2510                mGridView.setExtraLayoutSpace(0);
2511                verifyMargin();
2512            }
2513        });
2514        Thread.sleep(50);
2515        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
2516        assertTrue(v.getTop() < windowSize);
2517        assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
2518    }
2519
2520    public void testFocusFinder() throws Throwable {
2521        mInstrumentation = getInstrumentation();
2522        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2523        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2524                R.layout.vertical_linear_with_button);
2525        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
2526        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2527        initActivity(intent);
2528        mOrientation = BaseGridView.VERTICAL;
2529        mNumRows = 1;
2530
2531        // test focus from button to vertical grid view
2532        final View button = mActivity.findViewById(R.id.button);
2533        assertTrue(button.isFocused());
2534        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2535        assertFalse(mGridView.isFocused());
2536        assertTrue(mGridView.hasFocus());
2537
2538        // FocusFinder should find last focused(2nd) item on DPAD_DOWN
2539        final View secondChild = mGridView.getChildAt(1);
2540        runTestOnUiThread(new Runnable() {
2541            @Override
2542            public void run() {
2543                secondChild.requestFocus();
2544                button.requestFocus();
2545            }
2546        });
2547        assertTrue(button.isFocused());
2548        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2549        assertTrue(secondChild.isFocused());
2550
2551        // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
2552        // (2nd) item on DPAD_DOWN.
2553        runTestOnUiThread(new Runnable() {
2554            @Override
2555            public void run() {
2556                button.requestFocus();
2557            }
2558        });
2559        mGridView.setFocusable(false);
2560        mGridView.setFocusableInTouchMode(false);
2561        assertTrue(button.isFocused());
2562        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
2563        assertTrue(secondChild.isFocused());
2564    }
2565
2566    public void testRestoreIndexAndAddItems() throws Throwable {
2567        mInstrumentation = getInstrumentation();
2568        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2569        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2570                R.layout.vertical_linear);
2571        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
2572        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
2573        initActivity(intent);
2574        mOrientation = BaseGridView.VERTICAL;
2575        mNumRows = 1;
2576
2577        assertEquals(mGridView.getSelectedPosition(), 0);
2578        final SparseArray states = new SparseArray();
2579        runTestOnUiThread(new Runnable() {
2580            @Override
2581            public void run() {
2582                mGridView.saveHierarchyState(states);
2583                mGridView.setAdapter(null);
2584            }
2585
2586        });
2587        runTestOnUiThread(new Runnable() {
2588            @Override
2589            public void run() {
2590                mGridView.restoreHierarchyState(states);
2591                mActivity.attachToNewAdapter(new int[0]);
2592                mActivity.addItems(0, new int[]{100, 100, 100, 100});
2593            }
2594
2595        });
2596        waitForTransientStateGone(null);
2597        assertEquals(mGridView.getSelectedPosition(), 0);
2598    }
2599
2600    public void test27766012() throws Throwable {
2601        mInstrumentation = getInstrumentation();
2602        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2603        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2604                R.layout.vertical_linear_with_button_onleft);
2605        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
2606        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
2607        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2608        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
2609        initActivity(intent);
2610        mOrientation = BaseGridView.VERTICAL;
2611        mNumRows = 1;
2612
2613        // set remove animator two seconds
2614        mGridView.getItemAnimator().setRemoveDuration(2000);
2615        final View view = mGridView.getChildAt(1);
2616        runTestOnUiThread(new Runnable() {
2617            @Override
2618            public void run() {
2619                view.requestFocus();
2620            }
2621        });
2622        assertTrue(view.hasFocus());
2623        runTestOnUiThread(new Runnable() {
2624            @Override
2625            public void run() {
2626                mActivity.removeItems(0, 2);
2627            }
2628
2629        });
2630        // wait one second, removing second view is still attached to parent
2631        Thread.sleep(1000);
2632        assertSame(view.getParent(), mGridView);
2633        runTestOnUiThread(new Runnable() {
2634            @Override
2635            public void run() {
2636                // refocus to the removed item and do a focus search.
2637                view.requestFocus();
2638                view.focusSearch(View.FOCUS_UP);
2639            }
2640
2641        });
2642    }
2643
2644    public void testBug27258366() throws Throwable {
2645        mInstrumentation = getInstrumentation();
2646        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2647        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2648                R.layout.vertical_linear_with_button_onleft);
2649        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
2650        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
2651        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2652        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
2653        initActivity(intent);
2654        mOrientation = BaseGridView.VERTICAL;
2655        mNumRows = 1;
2656
2657        // move item1 500 pixels right, when focus is on item1, default focus finder will pick
2658        // item0 and item2 for the best match of focusSearch(FOCUS_LEFT).  The grid widget
2659        // must override default addFocusables(), not to add item0 or item2.
2660        mActivity.mAdapterListener = new GridActivity.AdapterListener() {
2661            public void onBind(RecyclerView.ViewHolder vh, int position) {
2662                if (position == 1) {
2663                    vh.itemView.setPaddingRelative(500, 0, 0, 0);
2664                } else {
2665                    vh.itemView.setPaddingRelative(0, 0, 0, 0);
2666                }
2667            }
2668        };
2669        runTestOnUiThread(new Runnable() {
2670            @Override
2671            public void run() {
2672                mGridView.getAdapter().notifyDataSetChanged();
2673            }
2674        });
2675        Thread.sleep(100);
2676
2677        final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1);
2678        runTestOnUiThread(new Runnable() {
2679            @Override
2680            public void run() {
2681                secondChild.requestFocus();
2682            }
2683        });
2684        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
2685        Thread.sleep(100);
2686        final View button = mActivity.findViewById(R.id.button);
2687        assertTrue(button.isFocused());
2688    }
2689
2690    public void testAccessibility() throws Throwable {
2691        mInstrumentation = getInstrumentation();
2692        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
2693        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
2694                R.layout.vertical_linear);
2695        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
2696        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
2697        initActivity(intent);
2698        mOrientation = BaseGridView.VERTICAL;
2699        mNumRows = 1;
2700
2701        assertTrue(0 == mGridView.getSelectedPosition());
2702
2703        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
2704                .getCompatAccessibilityDelegate();
2705        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
2706        runTestOnUiThread(new Runnable() {
2707            @Override
2708            public void run() {
2709                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
2710            }
2711        });
2712        assertTrue("test sanity", info.isScrollable());
2713        runTestOnUiThread(new Runnable() {
2714            @Override
2715            public void run() {
2716                delegateCompat.performAccessibilityAction(mGridView,
2717                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
2718            }
2719        });
2720        waitForScrollIdle(mVerifyLayout);
2721        int selectedPosition1 = mGridView.getSelectedPosition();
2722        assertTrue(0 < selectedPosition1);
2723
2724        runTestOnUiThread(new Runnable() {
2725            @Override
2726            public void run() {
2727                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
2728            }
2729        });
2730        assertTrue("test sanity", info.isScrollable());
2731        runTestOnUiThread(new Runnable() {
2732            @Override
2733            public void run() {
2734                delegateCompat.performAccessibilityAction(mGridView,
2735                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
2736            }
2737        });
2738        waitForScrollIdle(mVerifyLayout);
2739        int selectedPosition2 = mGridView.getSelectedPosition();
2740        assertTrue(selectedPosition2 < selectedPosition1);
2741    }
2742
2743}
2744