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