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