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