GridWidgetTest.java revision db0b1afb71440b79923692d58adee982b5954838
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.support.v17.leanback.tests.R;
19import android.test.ActivityInstrumentationTestCase2;
20import android.text.Selection;
21import android.text.Spannable;
22import android.util.Log;
23import android.util.SparseArray;
24import android.view.KeyEvent;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.TextView;
28import android.app.Instrumentation;
29import android.content.Intent;
30import android.os.Parcelable;
31
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.Comparator;
35import java.util.HashMap;
36import java.util.Iterator;
37
38/**
39 * @hide from javadoc
40 */
41public class GridWidgetTest extends ActivityInstrumentationTestCase2<GridActivity> {
42
43    private static final boolean HUMAN_DELAY = false;
44    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
45
46    protected GridActivity mActivity;
47    protected Instrumentation mInstrumentation;
48    protected BaseGridView mGridView;
49    protected GridLayoutManager mLayoutManager;
50    protected int mOrientation;
51    protected int mNumRows;
52
53    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
54        public int compare(View lhs, View rhs) {
55            if (mOrientation == BaseGridView.HORIZONTAL) {
56                return lhs.getLeft() - rhs.getLeft();
57            } else {
58                return lhs.getTop() - rhs.getTop();
59            }
60        };
61    };
62
63    /**
64     * Verify margins between items on same row are same.
65     */
66    private final Runnable mVerifyLayout = new Runnable() {
67        @Override
68        public void run() {
69            verifyMargin();
70        }
71    };
72
73    public GridWidgetTest() {
74        super("android.support.v17.leanback.tests", GridActivity.class);
75    }
76
77    private void humanDelay(int delay) throws InterruptedException {
78        if (HUMAN_DELAY) Thread.sleep(delay);
79    }
80    /**
81     * Change size of the Adapter and notifyDataSetChanged.
82     */
83    private void changeArraySize(final int size) throws Throwable {
84        runTestOnUiThread(new Runnable() {
85            public void run() {
86                mActivity.changeArraySize(size);
87            }
88        });
89        Thread.sleep(500);
90    }
91
92    /**
93     * Change selected position.
94     */
95    private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable {
96        runTestOnUiThread(new Runnable() {
97            public void run() {
98                mGridView.setSelectedPosition(position, scrollExtra);
99            }
100        });
101        Thread.sleep(500);
102    }
103
104    /**
105     * Wait for grid view stop scroll and optionally verify state of grid view.
106     */
107    protected void waitForScrollIdle(Runnable verify) throws Throwable {
108        Thread.sleep(100);
109        int total = 0;
110        while (mGridView.getLayoutManager().isSmoothScrolling() ||
111                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
112            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
113                throw new RuntimeException("waitForScrollIdle Timeout");
114            }
115            try {
116                Thread.sleep(100);
117            } catch (InterruptedException ex) {
118                break;
119            }
120            if (verify != null) {
121                runTestOnUiThread(verify);
122            }
123        }
124    }
125
126    /**
127     * Wait for grid view stop animation and optionally verify state of grid view.
128     */
129    protected void waitForTransientStateGone(Runnable verify) throws Throwable {
130        do {
131            try {
132                Thread.sleep(100);
133            } catch (InterruptedException ex) {
134                break;
135            }
136            if (verify != null) {
137                runTestOnUiThread(verify);
138            }
139        } while (mGridView.hasTransientState());
140    }
141
142    /**
143     * Wait for grid view stop scroll.
144     */
145    protected void waitForScrollIdle() throws Throwable {
146        waitForScrollIdle(null);
147    }
148
149    /**
150     * Scrolls using given key.
151     */
152    protected void scroll(int key, Runnable verify) throws Throwable {
153        do {
154            if (verify != null) {
155                runTestOnUiThread(verify);
156            }
157            sendRepeatedKeys(10, key);
158            try {
159                Thread.sleep(300);
160            } catch (InterruptedException ex) {
161                break;
162            }
163        } while (mGridView.getLayoutManager().isSmoothScrolling() ||
164                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
165    }
166
167    protected void scrollToBegin(Runnable verify) throws Throwable {
168        int key;
169        if (mOrientation == BaseGridView.HORIZONTAL) {
170            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
171                key = KeyEvent.KEYCODE_DPAD_RIGHT;
172            } else {
173                key = KeyEvent.KEYCODE_DPAD_LEFT;
174            }
175        } else {
176            key = KeyEvent.KEYCODE_DPAD_UP;
177        }
178        scroll(key, verify);
179    }
180
181    protected void scrollToEnd(Runnable verify) throws Throwable {
182        int key;
183        if (mOrientation == BaseGridView.HORIZONTAL) {
184            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
185                key = KeyEvent.KEYCODE_DPAD_LEFT;
186            } else {
187                key = KeyEvent.KEYCODE_DPAD_RIGHT;
188            }
189        } else {
190            key = KeyEvent.KEYCODE_DPAD_DOWN;
191        }
192        scroll(key, verify);
193    }
194
195    /**
196     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
197     */
198    protected View[][] sortByRows() {
199        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
200        ArrayList<Integer> rowLocations = new ArrayList();
201        for (int i = 0; i < mGridView.getChildCount(); i++) {
202            View v = mGridView.getChildAt(i);
203            int rowLocation;
204            if (mOrientation == BaseGridView.HORIZONTAL) {
205                rowLocation = v.getTop();
206            } else {
207                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL ?
208                    v.getRight() : v.getLeft();
209            }
210            ArrayList<View> views = rows.get(rowLocation);
211            if (views == null) {
212                views = new ArrayList<View>();
213                rows.put(rowLocation, views);
214                rowLocations.add(rowLocation);
215            }
216            views.add(v);
217        }
218        Object[] sortedLocations = rowLocations.toArray();
219        Arrays.sort(sortedLocations);
220        if (mNumRows != rows.size()) {
221            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
222        }
223        View[][] sorted = new View[rows.size()][];
224        for (int i = 0; i < rowLocations.size(); i++) {
225            Integer rowLocation = rowLocations.get(i);
226            ArrayList<View> arr = rows.get(rowLocation);
227            View[] views = arr.toArray(new View[arr.size()]);
228            Arrays.sort(views, mRowSortComparator);
229            sorted[i] = views;
230        }
231        return sorted;
232    }
233
234    protected void verifyMargin() {
235        View[][] sorted = sortByRows();
236        for (int row = 0; row < sorted.length; row++) {
237            View[] views = sorted[row];
238            int margin = -1;
239            for (int i = 1; i < views.length; i++) {
240                if (mOrientation == BaseGridView.HORIZONTAL) {
241                    assertEquals(mGridView.getHorizontalMargin(),
242                            views[i].getLeft() - views[i - 1].getRight());
243                } else {
244                    assertEquals(mGridView.getVerticalMargin(),
245                            views[i].getTop() - views[i - 1].getBottom());
246                }
247            }
248        }
249    }
250
251    protected void verifyBeginAligned() {
252        View[][] sorted = sortByRows();
253        int alignedLocation = 0;
254        if (mOrientation == BaseGridView.HORIZONTAL) {
255            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
256                for (int i = 0; i < sorted.length; i++) {
257                    if (i == 0) {
258                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
259                    } else {
260                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
261                    }
262                }
263            } else {
264                for (int i = 0; i < sorted.length; i++) {
265                    if (i == 0) {
266                        alignedLocation = sorted[i][0].getLeft();
267                    } else {
268                        assertEquals(alignedLocation, sorted[i][0].getLeft());
269                    }
270                }
271            }
272        } else {
273            for (int i = 0; i < sorted.length; i++) {
274                if (i == 0) {
275                    alignedLocation = sorted[i][0].getTop();
276                } else {
277                    assertEquals(alignedLocation, sorted[i][0].getTop());
278                }
279            }
280        }
281    }
282
283    protected int[] getEndEdges() {
284        View[][] sorted = sortByRows();
285        int[] edges = new int[sorted.length];
286        if (mOrientation == BaseGridView.HORIZONTAL) {
287            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
288                for (int i = 0; i < sorted.length; i++) {
289                    edges[i] = sorted[i][0].getLeft();
290                }
291            } else {
292                for (int i = 0; i < sorted.length; i++) {
293                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
294                }
295            }
296        } else {
297            for (int i = 0; i < sorted.length; i++) {
298                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
299            }
300        }
301        return edges;
302    }
303
304    protected void verifyEdgesSame(int[] edges, int[] edges2) {
305        assertEquals(edges.length, edges2.length);
306        for (int i = 0; i < edges.length; i++) {
307            assertEquals(edges[i], edges2[i]);
308        }
309    }
310
311    protected void verifyBoundCount(int count) {
312        if (mActivity.getBoundCount() != count) {
313            StringBuffer b = new StringBuffer();
314            b.append("ItemsLength: ");
315            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
316                b.append(mActivity.mItemLengths[i]).append(",");
317            }
318            assertEquals("Bound count does not match, ItemsLengths: "+ b,
319                    count, mActivity.getBoundCount());
320        }
321    }
322
323    private static int getCenterY(View v) {
324        return (v.getTop() + v.getBottom())/2;
325    }
326
327    private static int getCenterX(View v) {
328        return (v.getLeft() + v.getRight())/2;
329    }
330
331    private void initActivity(Intent intent) {
332        setActivityIntent(intent);
333        mActivity = getActivity();
334        final String testName = getName();
335        try {
336            runTestOnUiThread(new Runnable() {
337                public void run() {
338                    mActivity.setTitle(testName);
339                }
340            });
341            Thread.sleep(1000);
342        } catch (Throwable t) {
343            t.printStackTrace();
344        }
345        mGridView = mActivity.mGridView;
346    }
347
348    public void testThreeRowHorizontalBasic() throws Throwable {
349        mInstrumentation = getInstrumentation();
350        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
351        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
352        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
353        initActivity(intent);
354        mOrientation = BaseGridView.HORIZONTAL;
355        mNumRows = 3;
356
357        scrollToEnd(mVerifyLayout);
358        verifyBoundCount(100);
359
360        scrollToBegin(mVerifyLayout);
361
362        verifyBeginAligned();
363    }
364
365    public void testThreeColumnVerticalBasic() throws Throwable {
366
367        mInstrumentation = getInstrumentation();
368        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
369        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid);
370        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
371        initActivity(intent);
372        mOrientation = BaseGridView.VERTICAL;
373        mNumRows = 3;
374
375        scrollToEnd(mVerifyLayout);
376        verifyBoundCount(200);
377
378        scrollToBegin(mVerifyLayout);
379
380        verifyBeginAligned();
381    }
382
383    public void testRedundantAppendRemove() throws Throwable {
384        mInstrumentation = getInstrumentation();
385        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
386        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
387                R.layout.vertical_grid_testredundantappendremove);
388        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
389                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
390                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
391                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
392                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
393                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
394                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
395                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
396                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
397                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
398                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
399        initActivity(intent);
400        mOrientation = BaseGridView.VERTICAL;
401        mNumRows = 3;
402
403        scrollToEnd(mVerifyLayout);
404
405        verifyBoundCount(200);
406
407        scrollToBegin(mVerifyLayout);
408
409        verifyBeginAligned();
410    }
411
412    public void testRedundantAppendRemove2() throws Throwable {
413        mInstrumentation = getInstrumentation();
414        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
415        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
416                R.layout.horizontal_grid_testredundantappendremove2);
417        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
418                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
419                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
420                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
421                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
422                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
423        initActivity(intent);
424        mOrientation = BaseGridView.HORIZONTAL;
425        mNumRows = 3;
426        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
427
428        // test append without staggered result cache
429        scrollToEnd(mVerifyLayout);
430
431        verifyBoundCount(100);
432        int[] endEdges = getEndEdges();
433
434        scrollToBegin(mVerifyLayout);
435
436        verifyBeginAligned();
437
438        // now test append with staggered result cache
439        changeArraySize(3);
440        assertEquals("Staggerd cache should be kept as is when no item size change",
441                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
442
443        mActivity.resetBoundCount();
444        changeArraySize(100);
445
446        scrollToEnd(mVerifyLayout);
447        verifyBoundCount(100);
448
449        // we should get same aligned end edges
450        int[] endEdges2 = getEndEdges();
451        verifyEdgesSame(endEdges, endEdges2);
452    }
453
454    public void testItemMovedHorizontal() throws Throwable {
455
456        mInstrumentation = getInstrumentation();
457        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
458        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
459                R.layout.horizontal_grid);
460        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
461        initActivity(intent);
462        mOrientation = BaseGridView.HORIZONTAL;
463        mNumRows = 3;
464
465        mGridView.setSelectedPositionSmooth(150);
466        waitForScrollIdle(mVerifyLayout);
467        mActivity.swap(150, 152);
468        waitForTransientStateGone(null);
469
470        runTestOnUiThread(mVerifyLayout);
471
472        scrollToBegin(mVerifyLayout);
473
474        verifyBeginAligned();
475    }
476
477    public void testItemMovedVertical() throws Throwable {
478
479        mInstrumentation = getInstrumentation();
480        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
481        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
482                R.layout.vertical_grid);
483        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
484        initActivity(intent);
485        mOrientation = BaseGridView.VERTICAL;
486        mNumRows = 3;
487
488        mGridView.setSelectedPositionSmooth(150);
489        waitForScrollIdle(mVerifyLayout);
490        mActivity.swap(150, 152);
491        waitForTransientStateGone(null);
492
493        runTestOnUiThread(mVerifyLayout);
494
495        scrollToEnd(mVerifyLayout);
496        scrollToBegin(mVerifyLayout);
497
498        verifyBeginAligned();
499    }
500
501    public void testItemAddRemoveHorizontal() throws Throwable {
502
503        mInstrumentation = getInstrumentation();
504        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
505        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
506                R.layout.horizontal_grid);
507        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
508        initActivity(intent);
509        mOrientation = BaseGridView.HORIZONTAL;
510        mNumRows = 3;
511
512        scrollToEnd(mVerifyLayout);
513        int[] endEdges = getEndEdges();
514
515        mGridView.setSelectedPositionSmooth(150);
516        waitForScrollIdle(mVerifyLayout);
517        int[] removedItems = mActivity.removeItems(151, 4);
518        waitForTransientStateGone(null);
519
520        scrollToEnd(mVerifyLayout);
521        mGridView.setSelectedPositionSmooth(150);
522        waitForScrollIdle(mVerifyLayout);
523
524        mActivity.addItems(151, removedItems);
525        waitForTransientStateGone(null);
526        scrollToEnd(mVerifyLayout);
527
528        // we should get same aligned end edges
529        int[] endEdges2 = getEndEdges();
530        verifyEdgesSame(endEdges, endEdges2);
531
532        scrollToBegin(mVerifyLayout);
533        verifyBeginAligned();
534    }
535
536    public void testFocusToFirstItem() throws Throwable {
537
538        mInstrumentation = getInstrumentation();
539        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
540        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
541                R.layout.horizontal_grid);
542        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
543        initActivity(intent);
544        mOrientation = BaseGridView.HORIZONTAL;
545        mNumRows = 3;
546
547        int[] removedItems = mActivity.removeItems(0, 200);
548
549        waitForTransientStateGone(null);
550        humanDelay(500);
551        mActivity.addItems(0, removedItems);
552
553        waitForTransientStateGone(null);
554        humanDelay(500);
555        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
556
557        changeArraySize(0);
558
559        changeArraySize(200);
560        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
561    }
562
563    public void testNonFocusableHorizontal() throws Throwable {
564        final int numItems = 200;
565        final int startPos = 45;
566        final int skips = 20;
567        final int numColumns = 3;
568        final int endPos = startPos + numColumns * (skips + 1);
569
570        mInstrumentation = getInstrumentation();
571        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
572        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
573                R.layout.horizontal_grid);
574        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
575        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
576        mOrientation = BaseGridView.HORIZONTAL;
577        mNumRows = numColumns;
578        boolean[] focusable = new boolean[numItems];
579        for (int i = 0; i < focusable.length; i++) {
580            focusable[i] = true;
581        }
582        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
583            focusable[i] = false;
584        }
585        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
586        initActivity(intent);
587
588        mGridView.setSelectedPositionSmooth(startPos);
589        waitForScrollIdle(mVerifyLayout);
590
591        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
592            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
593        } else {
594            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
595        }
596        waitForScrollIdle(mVerifyLayout);
597        assertEquals(endPos, mGridView.getSelectedPosition());
598
599        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
600            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
601        } else {
602            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
603        }
604        waitForScrollIdle(mVerifyLayout);
605        assertEquals(startPos, mGridView.getSelectedPosition());
606
607    }
608
609
610    public void testNonFocusableVertical() throws Throwable {
611        final int numItems = 200;
612        final int startPos = 44;
613        final int skips = 20;
614        final int numColumns = 3;
615        final int endPos = startPos + numColumns * (skips + 1);
616
617        mInstrumentation = getInstrumentation();
618        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
619        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
620                R.layout.vertical_grid);
621        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
622        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
623        mOrientation = BaseGridView.VERTICAL;
624        mNumRows = numColumns;
625        boolean[] focusable = new boolean[numItems];
626        for (int i = 0; i < focusable.length; i++) {
627            focusable[i] = true;
628        }
629        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
630            focusable[i] = false;
631        }
632        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
633        initActivity(intent);
634
635        mGridView.setSelectedPositionSmooth(startPos);
636        waitForScrollIdle(mVerifyLayout);
637
638        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
639        waitForScrollIdle(mVerifyLayout);
640        assertEquals(endPos, mGridView.getSelectedPosition());
641
642        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
643        waitForScrollIdle(mVerifyLayout);
644        assertEquals(startPos, mGridView.getSelectedPosition());
645
646    }
647
648    public void testTransferFocusable() throws Throwable {
649        final int numItems = 200;
650        final int numColumns = 3;
651        final int startPos = 1;
652
653        mInstrumentation = getInstrumentation();
654        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
655        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
656                R.layout.horizontal_grid);
657        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
658        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
659        mOrientation = BaseGridView.HORIZONTAL;
660        mNumRows = numColumns;
661        boolean[] focusable = new boolean[numItems];
662        for (int i = 0; i < focusable.length; i++) {
663            focusable[i] = true;
664        }
665        for (int i = 0; i < startPos; i++) {
666            focusable[i] = false;
667        }
668        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
669        initActivity(intent);
670
671        changeArraySize(0);
672        assertTrue(mGridView.isFocused());
673
674        changeArraySize(numItems);
675        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
676    }
677
678    public void testTransferFocusable2() throws Throwable {
679        final int numItems = 200;
680        final int numColumns = 3;
681        final int startPos = 10;
682
683        mInstrumentation = getInstrumentation();
684        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
685        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
686                R.layout.horizontal_grid);
687        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
688        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
689        mOrientation = BaseGridView.HORIZONTAL;
690        mNumRows = numColumns;
691        boolean[] focusable = new boolean[numItems];
692        for (int i = 0; i < focusable.length; i++) {
693            focusable[i] = true;
694        }
695        for (int i = 0; i < startPos; i++) {
696            focusable[i] = false;
697        }
698        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
699        initActivity(intent);
700
701        changeArraySize(0);
702        assertTrue(mGridView.isFocused());
703
704        changeArraySize(numItems);
705        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
706    }
707
708    public void testNonFocusableLoseInFastLayout() throws Throwable {
709        mInstrumentation = getInstrumentation();
710        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
711        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
712                R.layout.vertical_linear);
713        int[] items = new int[300];
714        for (int i = 0; i < items.length; i++) {
715            items[i] = 480;
716        }
717        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
718        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
719        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
720        mOrientation = BaseGridView.VERTICAL;
721        mNumRows = 1;
722        int pressDown = 15;
723
724        initActivity(intent);
725
726        mGridView.setSelectedPositionSmooth(0);
727        waitForScrollIdle(mVerifyLayout);
728
729        for (int i = 0; i < pressDown; i++) {
730            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
731        }
732        waitForScrollIdle(mVerifyLayout);
733        assertFalse(mGridView.isFocused());
734
735    }
736
737    public void testSetSelectionWithDelta() throws Throwable {
738        mInstrumentation = getInstrumentation();
739        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
740        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
741                R.layout.vertical_linear);
742        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
743        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
744        mOrientation = BaseGridView.VERTICAL;
745        mNumRows = 1;
746
747        initActivity(intent);
748
749        runTestOnUiThread(new Runnable() {
750            public void run() {
751                mGridView.setSelectedPositionSmooth(3);
752            }
753        });
754        waitForScrollIdle(mVerifyLayout);
755        int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
756
757        humanDelay(1000);
758
759        // scroll to position with delta
760        setSelectedPosition(3, 100);
761        int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
762        assertEquals(top1 - 100, top2);
763
764        // scroll to same position without delta, it will be reset
765        setSelectedPosition(3, 0);
766        int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
767        assertEquals(top1, top3);
768
769        // scroll invisible item after last visible item
770        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
771                .mGrid.getLastVisibleIndex();
772        setSelectedPosition(lastVisiblePos + 1, 100);
773        int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop();
774        assertEquals(top1 - 100, top4);
775
776        // scroll invisible item before first visible item
777        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
778                .mGrid.getFirstVisibleIndex();
779        setSelectedPosition(firstVisiblePos - 1, 100);
780        int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop();
781        assertEquals(top1 - 100, top5);
782
783        // scroll to invisible item that is far away.
784        setSelectedPosition(50, 100);
785        int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop();
786        assertEquals(top1 - 100, top6);
787
788        // scroll to invisible item that is far away.
789        runTestOnUiThread(new Runnable() {
790            public void run() {
791                mGridView.setSelectedPositionSmooth(100);
792            }
793        });
794        waitForScrollIdle(mVerifyLayout);
795        int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop();
796        assertEquals(top1, top7);
797
798        // scroll to invisible item that is far away.
799        setSelectedPosition(10, 50);
800        int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop();
801        assertEquals(top1 - 50, top8);
802    }
803
804    public void testSetSelectionWithDeltaInGrid() throws Throwable {
805        mInstrumentation = getInstrumentation();
806        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
807        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
808                R.layout.vertical_grid);
809        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
810        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
811        mOrientation = BaseGridView.VERTICAL;
812        mNumRows = 3;
813
814        initActivity(intent);
815
816        runTestOnUiThread(new Runnable() {
817            public void run() {
818                mGridView.setSelectedPositionSmooth(10);
819            }
820        });
821        waitForScrollIdle(mVerifyLayout);
822        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
823
824        humanDelay(500);
825
826        // scroll to position with delta
827        setSelectedPosition(20, 100);
828        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
829        assertEquals(top1 - 100, top2);
830
831        // scroll to same position without delta, it will be reset
832        setSelectedPosition(20, 0);
833        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
834        assertEquals(top1, top3);
835
836        // scroll invisible item after last visible item
837        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
838                .mGrid.getLastVisibleIndex();
839        setSelectedPosition(lastVisiblePos + 1, 100);
840        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
841        verifyMargin();
842        assertEquals(top1 - 100, top4);
843
844        // scroll invisible item before first visible item
845        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
846                .mGrid.getFirstVisibleIndex();
847        setSelectedPosition(firstVisiblePos - 1, 100);
848        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
849        assertEquals(top1 - 100, top5);
850
851        // scroll to invisible item that is far away.
852        setSelectedPosition(100, 100);
853        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
854        assertEquals(top1 - 100, top6);
855
856        // scroll to invisible item that is far away.
857        runTestOnUiThread(new Runnable() {
858            public void run() {
859                mGridView.setSelectedPositionSmooth(200);
860            }
861        });
862        waitForScrollIdle(mVerifyLayout);
863        Thread.sleep(500);
864        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
865        assertEquals(top1, top7);
866
867        // scroll to invisible item that is far away.
868        setSelectedPosition(10, 50);
869        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
870        assertEquals(top1 - 50, top8);
871    }
872
873
874    public void testSetSelectionWithDeltaInGrid1() throws Throwable {
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        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
880                193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137,
881                188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205,
882                223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222,
883                150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236,
884                123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128,
885                192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218,
886                192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191,
887                212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141,
888                156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191,
889                132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184,
890                164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226,
891                132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226,
892                127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169,
893                182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220,
894                178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125,
895                214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122,
896                188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191,
897                199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210,
898                164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130,
899                137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227,
900                193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154,
901                192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124,
902                172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174,
903                146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225,
904                174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162,
905                192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221,
906                120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190,
907                204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125,
908                202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123,
909                237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162,
910                139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199,
911                215,213,122,197
912        });
913        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
914        mOrientation = BaseGridView.VERTICAL;
915        mNumRows = 3;
916
917        initActivity(intent);
918
919        runTestOnUiThread(new Runnable() {
920            public void run() {
921                mGridView.setSelectedPositionSmooth(10);
922            }
923        });
924        waitForScrollIdle(mVerifyLayout);
925        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
926
927        humanDelay(500);
928
929        // scroll to position with delta
930        setSelectedPosition(20, 100);
931        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
932        assertEquals(top1 - 100, top2);
933
934        // scroll to same position without delta, it will be reset
935        setSelectedPosition(20, 0);
936        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
937        assertEquals(top1, top3);
938
939        // scroll invisible item after last visible item
940        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
941                .mGrid.getLastVisibleIndex();
942        setSelectedPosition(lastVisiblePos + 1, 100);
943        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
944        verifyMargin();
945        assertEquals(top1 - 100, top4);
946
947        // scroll invisible item before first visible item
948        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
949                .mGrid.getFirstVisibleIndex();
950        setSelectedPosition(firstVisiblePos - 1, 100);
951        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
952        assertEquals(top1 - 100, top5);
953
954        // scroll to invisible item that is far away.
955        setSelectedPosition(100, 100);
956        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
957        assertEquals(top1 - 100, top6);
958
959        // scroll to invisible item that is far away.
960        runTestOnUiThread(new Runnable() {
961            public void run() {
962                mGridView.setSelectedPositionSmooth(200);
963            }
964        });
965        waitForScrollIdle(mVerifyLayout);
966        Thread.sleep(500);
967        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
968        assertEquals(top1, top7);
969
970        // scroll to invisible item that is far away.
971        setSelectedPosition(10, 50);
972        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
973        assertEquals(top1 - 50, top8);
974    }
975
976    public void testSmoothScrollSelectionEvents() throws Throwable {
977        mInstrumentation = getInstrumentation();
978        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
979        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
980                R.layout.vertical_grid);
981        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
982        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
983        mOrientation = BaseGridView.VERTICAL;
984        mNumRows = 3;
985        initActivity(intent);
986
987        runTestOnUiThread(new Runnable() {
988            public void run() {
989                mGridView.setSelectedPositionSmooth(30);
990            }
991        });
992        waitForScrollIdle(mVerifyLayout);
993        humanDelay(500);
994
995        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
996        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
997            @Override
998            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
999                selectedPositions.add(position);
1000            }
1001        });
1002
1003        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
1004        humanDelay(500);
1005        waitForScrollIdle(mVerifyLayout);
1006        // should only get childselected event for item 0 once
1007        assertTrue(selectedPositions.size() > 0);
1008        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
1009        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
1010            assertFalse(0 == selectedPositions.get(i).intValue());
1011        }
1012
1013    }
1014
1015    public void testSmoothScrollSelectionEventsLinear() throws Throwable {
1016        mInstrumentation = getInstrumentation();
1017        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1018        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1019                R.layout.vertical_linear);
1020        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
1021        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1022        mOrientation = BaseGridView.VERTICAL;
1023        mNumRows = 1;
1024        initActivity(intent);
1025
1026        runTestOnUiThread(new Runnable() {
1027            public void run() {
1028                mGridView.setSelectedPositionSmooth(10);
1029            }
1030        });
1031        waitForScrollIdle(mVerifyLayout);
1032        humanDelay(500);
1033
1034        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
1035        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
1036            @Override
1037            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
1038                selectedPositions.add(position);
1039            }
1040        });
1041
1042        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
1043        humanDelay(500);
1044        waitForScrollIdle(mVerifyLayout);
1045        // should only get childselected event for item 0 once
1046        assertTrue(selectedPositions.size() > 0);
1047        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
1048        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
1049            assertFalse(0 == selectedPositions.get(i).intValue());
1050        }
1051
1052    }
1053
1054    public void testScrollToNoneExisting() throws Throwable {
1055        mInstrumentation = getInstrumentation();
1056        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1057        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1058                R.layout.vertical_grid);
1059        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
1060        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1061        mOrientation = BaseGridView.VERTICAL;
1062        mNumRows = 3;
1063        initActivity(intent);
1064
1065        runTestOnUiThread(new Runnable() {
1066            public void run() {
1067                mGridView.setSelectedPositionSmooth(99);
1068            }
1069        });
1070        waitForScrollIdle(mVerifyLayout);
1071        humanDelay(500);
1072
1073
1074        runTestOnUiThread(new Runnable() {
1075            public void run() {
1076                mGridView.setSelectedPositionSmooth(50);
1077            }
1078        });
1079        Thread.sleep(100);
1080        runTestOnUiThread(new Runnable() {
1081            public void run() {
1082                mGridView.requestLayout();
1083                mGridView.setSelectedPositionSmooth(0);
1084            }
1085        });
1086        waitForScrollIdle(mVerifyLayout);
1087        humanDelay(500);
1088
1089    }
1090
1091    public void testSmoothscrollerInterrupted() throws Throwable {
1092        mInstrumentation = getInstrumentation();
1093        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1094        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1095                R.layout.vertical_linear);
1096        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1097        int[] items = new int[100];
1098        for (int i = 0; i < items.length; i++) {
1099            items[i] = 680;
1100        }
1101        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1102        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1103        mOrientation = BaseGridView.VERTICAL;
1104        mNumRows = 1;
1105
1106        initActivity(intent);
1107
1108        mGridView.setSelectedPositionSmooth(0);
1109        waitForScrollIdle(mVerifyLayout);
1110        assertTrue(mGridView.getChildAt(0).hasFocus());
1111
1112        // Pressing lots of key to make sure smooth scroller is running
1113        for (int i = 0; i < 20; i++) {
1114            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1115        }
1116        Thread.sleep(100);
1117        int total = 0;
1118        while (mGridView.getLayoutManager().isSmoothScrolling() ||
1119                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
1120            if ((total += 10) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
1121                throw new RuntimeException("waitForScrollIdle Timeout");
1122            }
1123            try {
1124                // Repeatedly pressing to make sure pending keys does not drop to zero.
1125                Thread.sleep(10);
1126                sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1127            } catch (InterruptedException ex) {
1128                break;
1129            }
1130        }
1131
1132        assertTrue("LinearSmoothScroller would not use many RV.smoothScrollBy() calls",
1133                ((VerticalGridViewEx) mGridView).mSmoothScrollByCalled < 10);
1134    }
1135
1136    public void testSetNumRowsAndAddItem() throws Throwable {
1137        mInstrumentation = getInstrumentation();
1138        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1139        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1140                R.layout.vertical_linear);
1141        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1142        int[] items = new int[2];
1143        for (int i = 0; i < items.length; i++) {
1144            items[i] = 300;
1145        }
1146        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1147        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1148        mOrientation = BaseGridView.VERTICAL;
1149        mNumRows = 1;
1150
1151        initActivity(intent);
1152
1153        mGridView.setSelectedPositionSmooth(0);
1154        waitForScrollIdle(mVerifyLayout);
1155
1156        mActivity.addItems(items.length, new int[]{300});
1157
1158        runTestOnUiThread(new Runnable() {
1159            public void run() {
1160                ((VerticalGridView) mGridView).setNumColumns(2);
1161            }
1162        });
1163        Thread.sleep(1000);
1164        assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft());
1165    }
1166
1167
1168    public void testRequestLayoutBugInLayout() throws Throwable {
1169        mInstrumentation = getInstrumentation();
1170        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1171        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1172                R.layout.vertical_linear);
1173        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
1174        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1175        int[] items = new int[100];
1176        for (int i = 0; i < items.length; i++) {
1177            items[i] = 300;
1178        }
1179        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1180        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1181        mOrientation = BaseGridView.VERTICAL;
1182        mNumRows = 1;
1183
1184        initActivity(intent);
1185
1186        runTestOnUiThread(new Runnable() {
1187            public void run() {
1188                mGridView.setSelectedPositionSmooth(1);
1189            }
1190        });
1191        waitForScrollIdle(mVerifyLayout);
1192
1193        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
1194        waitForScrollIdle(mVerifyLayout);
1195
1196        assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString());
1197    }
1198
1199
1200    public void testChangeLayoutInChild() throws Throwable {
1201        mInstrumentation = getInstrumentation();
1202        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1203        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1204                R.layout.vertical_linear_wrap_content);
1205        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
1206        int[] items = new int[2];
1207        for (int i = 0; i < items.length; i++) {
1208            items[i] = 300;
1209        }
1210        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1211        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1212        mOrientation = BaseGridView.VERTICAL;
1213        mNumRows = 1;
1214
1215        initActivity(intent);
1216
1217        runTestOnUiThread(new Runnable() {
1218            public void run() {
1219                mGridView.setSelectedPositionSmooth(0);
1220            }
1221        });
1222        waitForScrollIdle(mVerifyLayout);
1223        verifyMargin();
1224
1225        runTestOnUiThread(new Runnable() {
1226            public void run() {
1227                mGridView.setSelectedPositionSmooth(1);
1228            }
1229        });
1230        waitForScrollIdle(mVerifyLayout);
1231        verifyMargin();
1232    }
1233
1234
1235    public void testZeroFixedSecondarySize() throws Throwable {
1236        mInstrumentation = getInstrumentation();
1237        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1238        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1239                R.layout.vertical_linear_measured_with_zero);
1240        intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true);
1241        int[] items = new int[2];
1242        for (int i = 0; i < items.length; i++) {
1243            items[i] = 0;
1244        }
1245        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1246        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1247        mOrientation = BaseGridView.VERTICAL;
1248        mNumRows = 1;
1249
1250        initActivity(intent);
1251
1252    }
1253
1254    public void testChildStates() throws Throwable {
1255        mInstrumentation = getInstrumentation();
1256        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1257        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
1258        int[] items = new int[100];
1259        for (int i = 0; i < items.length; i++) {
1260            items[i] = 200;
1261        }
1262        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1263        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1264        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
1265        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
1266        mOrientation = BaseGridView.VERTICAL;
1267        mNumRows = 1;
1268
1269        initActivity(intent);
1270        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
1271
1272        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
1273
1274        // 1 Save view states
1275        runTestOnUiThread(new Runnable() {
1276            public void run() {
1277                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
1278                        .getText()), 0, 1);
1279                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
1280                        .getText()), 0, 1);
1281                mGridView.saveHierarchyState(container);
1282            }
1283        });
1284
1285        // 2 Change view states
1286        runTestOnUiThread(new Runnable() {
1287            public void run() {
1288                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
1289                        .getText()), 1, 2);
1290                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
1291                        .getText()), 1, 2);
1292            }
1293        });
1294
1295        // 3 Detached and re-attached,  should still maintain state of (2)
1296        runTestOnUiThread(new Runnable() {
1297            public void run() {
1298                mGridView.setSelectedPositionSmooth(1);
1299            }
1300        });
1301        waitForScrollIdle(mVerifyLayout);
1302        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
1303        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
1304        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
1305        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
1306
1307        // 4 Recycled and rebound, should load state from (2)
1308        runTestOnUiThread(new Runnable() {
1309            public void run() {
1310                mGridView.setSelectedPositionSmooth(20);
1311            }
1312        });
1313        waitForScrollIdle(mVerifyLayout);
1314        runTestOnUiThread(new Runnable() {
1315            public void run() {
1316                mGridView.setSelectedPositionSmooth(0);
1317            }
1318        });
1319        waitForScrollIdle(mVerifyLayout);
1320        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
1321        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
1322        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
1323        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
1324    }
1325
1326
1327    public void testNoDispatchSaveChildState() throws Throwable {
1328        mInstrumentation = getInstrumentation();
1329        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1330        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
1331        int[] items = new int[100];
1332        for (int i = 0; i < items.length; i++) {
1333            items[i] = 200;
1334        }
1335        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1336        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1337        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
1338        mOrientation = BaseGridView.VERTICAL;
1339        mNumRows = 1;
1340
1341        initActivity(intent);
1342        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD);
1343
1344        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
1345
1346        // 1. Set text selection, save view states should do nothing on child
1347        runTestOnUiThread(new Runnable() {
1348            public void run() {
1349                for (int i = 0; i < mGridView.getChildCount(); i++) {
1350                    Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i))
1351                            .getText()), 0, 1);
1352                }
1353                mGridView.saveHierarchyState(container);
1354            }
1355        });
1356
1357        // 2. clear the text selection
1358        runTestOnUiThread(new Runnable() {
1359            public void run() {
1360                for (int i = 0; i < mGridView.getChildCount(); i++) {
1361                    Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i))
1362                            .getText()));
1363                }
1364            }
1365        });
1366
1367        // 3. Restore view states should be a no-op for child
1368        runTestOnUiThread(new Runnable() {
1369            public void run() {
1370                mGridView.restoreHierarchyState(container);
1371                for (int i = 0; i < mGridView.getChildCount(); i++) {
1372                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart());
1373                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd());
1374                }
1375            }
1376        });
1377    }
1378
1379
1380    static interface ViewTypeProvider {
1381        public int getViewType(int position);
1382    }
1383
1384    static interface ItemAlignmentFacetProvider {
1385        public ItemAlignmentFacet getItemAlignmentFacet(int viewType);
1386    }
1387
1388    static class TwoViewTypesProvider implements ViewTypeProvider {
1389        static int VIEW_TYPE_FIRST = 1;
1390        static int VIEW_TYPE_DEFAULT = 0;
1391        @Override
1392        public int getViewType(int position) {
1393            if (position == 0) {
1394                return VIEW_TYPE_FIRST;
1395            } else {
1396                return VIEW_TYPE_DEFAULT;
1397            }
1398        }
1399    }
1400
1401    static class PositionItemAlignmentFacetProviderForRelativeLayout1
1402            implements ItemAlignmentFacetProvider {
1403        ItemAlignmentFacet mMultipleFacet;
1404
1405        PositionItemAlignmentFacetProviderForRelativeLayout1() {
1406            mMultipleFacet = new ItemAlignmentFacet();
1407            ItemAlignmentFacet.ItemAlignmentDef[] defs =
1408                    new ItemAlignmentFacet.ItemAlignmentDef[2];
1409            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
1410            defs[0].setItemAlignmentViewId(R.id.t1);
1411            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
1412            defs[1].setItemAlignmentViewId(R.id.t2);
1413            defs[1].setItemAlignmentOffsetPercent(100);
1414            defs[1].setItemAlignmentOffset(-10);
1415            mMultipleFacet.setAlignmentDefs(defs);
1416        }
1417
1418        @Override
1419        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
1420            if (position == 0) {
1421                return mMultipleFacet;
1422            } else {
1423                return null;
1424            }
1425        }
1426    }
1427
1428    public void testMultipleScrollPosition1() throws Throwable {
1429        mInstrumentation = getInstrumentation();
1430        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1431        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1432                R.layout.vertical_linear);
1433        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
1434        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1435        int[] items = new int[100];
1436        for (int i = 0; i < items.length; i++) {
1437            items[i] = 300;
1438        }
1439        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1440        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1441        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
1442                TwoViewTypesProvider.class.getName());
1443        // Set ItemAlignment for each ViewHolder and view type,  ViewHolder should
1444        // override the view type settings.
1445        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
1446                PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName());
1447        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
1448                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
1449        mOrientation = BaseGridView.VERTICAL;
1450        mNumRows = 1;
1451
1452        initActivity(intent);
1453
1454        assertEquals("First view is aligned with padding top",
1455                mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop());
1456
1457        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1458        waitForScrollIdle(mVerifyLayout);
1459
1460        final View v = mGridView.getChildAt(0);
1461        View t1 = v.findViewById(R.id.t1);
1462        int t1align = (t1.getTop() + t1.getBottom()) / 2;
1463        View t2 = v.findViewById(R.id.t2);
1464        int t2align = t2.getBottom() - 10;
1465        assertEquals("Expected alignment for 2nd textview",
1466                mGridView.getPaddingTop() - (t2align - t1align),
1467                v.getTop());
1468    }
1469
1470    static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements
1471            ItemAlignmentFacetProvider {
1472        ItemAlignmentFacet mMultipleFacet;
1473
1474        PositionItemAlignmentFacetProviderForRelativeLayout2() {
1475            mMultipleFacet = new ItemAlignmentFacet();
1476            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
1477            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
1478            defs[0].setItemAlignmentViewId(R.id.t1);
1479            defs[0].setItemAlignmentOffsetPercent(0);
1480            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
1481            defs[1].setItemAlignmentViewId(R.id.t2);
1482            defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
1483            defs[1].setItemAlignmentOffset(-10);
1484            mMultipleFacet.setAlignmentDefs(defs);
1485        }
1486
1487        @Override
1488        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
1489            if (position == 0) {
1490                return mMultipleFacet;
1491            } else {
1492                return null;
1493            }
1494        }
1495    }
1496
1497    public void testMultipleScrollPosition2() throws Throwable {
1498        mInstrumentation = getInstrumentation();
1499        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1500        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
1501        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
1502        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1503        int[] items = new int[100];
1504        for (int i = 0; i < items.length; i++) {
1505            items[i] = 300;
1506        }
1507        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1508        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1509        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
1510                TwoViewTypesProvider.class.getName());
1511        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
1512                PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
1513        mOrientation = BaseGridView.VERTICAL;
1514        mNumRows = 1;
1515
1516        initActivity(intent);
1517
1518        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
1519                mGridView.getChildAt(0).getTop());
1520
1521        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1522        waitForScrollIdle(mVerifyLayout);
1523
1524        final View v = mGridView.getChildAt(0);
1525        View t1 = v.findViewById(R.id.t1);
1526        int t1align = t1.getTop();
1527        View t2 = v.findViewById(R.id.t2);
1528        int t2align = t2.getTop() - 10;
1529        assertEquals("Expected alignment for 2nd textview",
1530                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
1531    }
1532
1533    static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements
1534            ItemAlignmentFacetProvider {
1535        ItemAlignmentFacet mMultipleFacet;
1536
1537        ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() {
1538            mMultipleFacet = new ItemAlignmentFacet();
1539            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
1540            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
1541            defs[0].setItemAlignmentViewId(R.id.t1);
1542            defs[0].setItemAlignmentOffsetPercent(0);
1543            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
1544            defs[1].setItemAlignmentViewId(R.id.t2);
1545            defs[1].setItemAlignmentOffsetPercent(100);
1546            defs[1].setItemAlignmentOffset(-10);
1547            mMultipleFacet.setAlignmentDefs(defs);
1548        }
1549
1550        @Override
1551        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
1552            if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) {
1553                return mMultipleFacet;
1554            } else {
1555                return null;
1556            }
1557        }
1558    }
1559
1560    public void testMultipleScrollPosition3() throws Throwable {
1561        mInstrumentation = getInstrumentation();
1562        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1563        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
1564        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
1565        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
1566        int[] items = new int[100];
1567        for (int i = 0; i < items.length; i++) {
1568            items[i] = 300;
1569        }
1570        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
1571        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
1572        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
1573                TwoViewTypesProvider.class.getName());
1574        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
1575                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
1576        mOrientation = BaseGridView.VERTICAL;
1577        mNumRows = 1;
1578
1579        initActivity(intent);
1580
1581        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
1582                mGridView.getChildAt(0).getTop());
1583
1584        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
1585        waitForScrollIdle(mVerifyLayout);
1586
1587        final View v = mGridView.getChildAt(0);
1588        View t1 = v.findViewById(R.id.t1);
1589        int t1align = t1.getTop();
1590        View t2 = v.findViewById(R.id.t2);
1591        int t2align = t2.getBottom() - 10;
1592        assertEquals("Expected alignment for 2nd textview",
1593                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
1594    }
1595
1596    public void testSelectionAndAddItemInOneCycle() throws Throwable {
1597        mInstrumentation = getInstrumentation();
1598        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1599        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1600                R.layout.vertical_linear);
1601        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
1602        initActivity(intent);
1603        mOrientation = BaseGridView.HORIZONTAL;
1604        mNumRows = 1;
1605
1606        runTestOnUiThread(new Runnable() {
1607            public void run() {
1608                mActivity.addItems(0, new int[]{300, 300});
1609                mGridView.setSelectedPosition(0);
1610            }
1611        });
1612        waitForTransientStateGone(null);
1613        assertEquals(0, mGridView.getSelectedPosition());
1614    }
1615
1616    public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable {
1617        mInstrumentation = getInstrumentation();
1618        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
1619        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
1620                R.layout.vertical_linear);
1621        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
1622        initActivity(intent);
1623        mOrientation = BaseGridView.HORIZONTAL;
1624        mNumRows = 1;
1625
1626        runTestOnUiThread(new Runnable() {
1627            public void run() {
1628                mActivity.addItems(0, new int[]{300, 300});
1629                mGridView.setSelectedPositionSmooth(0);
1630            }
1631        });
1632        waitForTransientStateGone(null);
1633        assertEquals(0, mGridView.getSelectedPosition());
1634    }
1635}
1636