Revert "Revert "Fixed keyboard autoclosing and wrong position for EditTexts" ag/2385121"

This reverts commit 0dbe90863b0569b3770857e3718d360cc8904757.
Fixed the broken tests that were caused by IME kept open when starting
the next test, and put a more reliable wait for waiting on IME popping
up or closing.

Bug: 36082993
Bug: 62373324
Bug: 34887461
Test: ./gradlew support-recyclerview-v7:connectedCheck
-Pandroid.testInstrumentationRunnerArguments.class=android.support.v7.widget.LinearLayoutManagerTest
./gradlew support-recyclerview-v7:connectedCheck
-Pandroid.testInstrumentationRunnerArguments.class=android.support.v7.widget.GridLayoutManagerTest
Change-Id: I53bb76a5fa7e2748b7e0749ce22d642bcee600f0
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index a72975d..a229846 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -491,6 +491,7 @@
         // resolve layout direction
         resolveShouldLayoutReverse();
 
+        final View focused = getFocusedChild();
         if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                 || mPendingSavedState != null) {
             mAnchorInfo.reset();
@@ -498,6 +499,22 @@
             // calculate anchor position and coordinate
             updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
             mAnchorInfo.mValid = true;
+        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
+                        >= mOrientationHelper.getEndAfterPadding()
+                || mOrientationHelper.getDecoratedEnd(focused)
+                <= mOrientationHelper.getStartAfterPadding())) {
+            // This case relates to when the anchor child is the focused view and due to layout
+            // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
+            // up after tapping an EditText which shrinks RV causing the focused view (The tapped
+            // EditText which is the anchor child) to get kicked out of the screen. Will update the
+            // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
+            // the available space in layoutState will be calculated as negative preventing the
+            // focused view from being laid out in fill.
+            // Note that we won't update the anchor position between layout passes (refer to
+            // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
+            // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
+            // child which can change between layout passes).
+            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused);
         }
         if (DEBUG) {
             Log.d(TAG, "Anchor info:" + mAnchorInfo);
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
new file mode 100644
index 0000000..f4caad3
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.util;
+
+import android.graphics.Rect;
+import android.support.testutils.PollingCheck;
+import android.view.View;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit rule that ensures that IME is closed after a test is finished (or exception thrown).
+ * A test that triggers IME open/close should call setContainerView with the activity's container
+ * view in order to trigger this cleanup at the end of the test. Otherwise, no cleanup happens.
+ */
+public class ImeCleanUpTestRule implements TestRule {
+
+    private View mContainerView;
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    base.evaluate();
+                } finally {
+                    closeImeIfOpen();
+                }
+            }
+        };
+    }
+
+    /**
+     * Sets the container view used to calculate the total screen height and the height available
+     * to the test activity.
+     */
+    public void setContainerView(View containerView) {
+        mContainerView = containerView;
+    }
+
+    private void closeImeIfOpen() {
+        if (mContainerView == null) {
+            return;
+        }
+        // Ensuring that IME is closed after starting each test.
+        final Rect r = new Rect();
+        mContainerView.getWindowVisibleDisplayFrame(r);
+        // This is the entire height of the screen available to both the view and IME
+        final int screenHeight = mContainerView.getRootView().getHeight();
+
+        // r.bottom is the position above IME if it's open or device button.
+        // if IME is shown, r.bottom is smaller than screenHeight.
+        int imeHeight = screenHeight - r.bottom;
+
+        // Picking a threshold to detect when IME is open
+        if (imeHeight > screenHeight * 0.15) {
+            // Soft keyboard is shown, will wait for it to close after running the test. Note that
+            // we don't press back button here as the IME should close by itself when a test
+            // finishes. If the wait isn't done here, the IME can mess up with the layout of the
+            // next test.
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    mContainerView.getWindowVisibleDisplayFrame(r);
+                    int imeHeight = screenHeight - r.bottom;
+                    return imeHeight < screenHeight * 0.15;
+                }
+            });
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index cc6bbe8..13dd1e4 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -325,6 +325,36 @@
         }
     }
 
+    class GridEditTextAdapter extends EditTextAdapter {
+
+        Set<Integer> mFullSpanItems = new HashSet<Integer>();
+        int mSpanPerItem = 1;
+
+        GridEditTextAdapter(int count) {
+            this(count, 1);
+        }
+
+        GridEditTextAdapter(int count, int spanPerItem) {
+            super(count);
+            mSpanPerItem = spanPerItem;
+        }
+
+        void setFullSpan(int... items) {
+            for (int i : items) {
+                mFullSpanItems.add(i);
+            }
+        }
+
+        void assignSpanSizeLookup(final GridLayoutManager glm) {
+            glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+                @Override
+                public int getSpanSize(int position) {
+                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
+                }
+            });
+        }
+    }
+
     class GridTestAdapter extends TestAdapter {
 
         Set<Integer> mFullSpanItems = new HashSet<Integer>();
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 8ca5f4e..90e0536 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -38,10 +38,12 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v7.recyclerview.test.R;
 import android.support.v7.recyclerview.test.SameActivityTestRule;
+import android.text.Editable;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
@@ -793,6 +795,37 @@
         }
     }
 
+    public class EditTextAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+        final ArrayList<Editable> mEditables;
+        public EditTextAdapter(int count) {
+            mEditables = new ArrayList<>();
+            for (int i = 0; i < count; ++i) {
+                mEditables.add(Editable.Factory.getInstance().newEditable("Sample Text " + i));
+            }
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            final EditText editText = new EditText(parent.getContext());
+            editText.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            final TestViewHolder viewHolder = new TestViewHolder(editText);
+            return viewHolder;
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            ((EditText) holder.itemView).setText(Editable.Factory.getInstance().newEditable(
+                    mEditables.get(position)));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mEditables.size();
+        }
+    }
+
     public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
             implements AttachDetachCountingAdapter {
 
@@ -1197,6 +1230,27 @@
         }
     }
 
+
+    public static View findFirstFullyVisibleChild(RecyclerView parent) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            View child = parent.getChildAt(i);
+            if (isViewFullyInBound(parent, child)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    public static View findLastFullyVisibleChild(RecyclerView parent) {
+        for (int i = parent.getChildCount() - 1; i >= 0; i--) {
+            View child = parent.getChildAt(i);
+            if (isViewFullyInBound(parent, child)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns whether a child of RecyclerView is partially in bound. A child is
      * partially in-bounds if it's either fully or partially visible on the screen.
@@ -1232,7 +1286,7 @@
      * @param child The child view to be checked whether is fully within RV's bounds.
      * @return True if the child view is fully visible; false otherwise.
      */
-    public boolean isViewFullyInBound(RecyclerView parent, View child) {
+    public static boolean isViewFullyInBound(RecyclerView parent, View child) {
         if (child == null) {
             return false;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 5ebebfe..23eaf52 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -34,15 +37,20 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.util.ImeCleanUpTestRule;
+import android.support.v7.util.TouchUtils;
 import android.test.UiThreadTest;
 import android.util.SparseIntArray;
 import android.util.StateSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,6 +64,9 @@
 @RunWith(AndroidJUnit4.class)
 public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
 
+    @Rule
+    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
+
     @Test
     public void focusSearchFailureUp() throws Throwable {
         focusSearchFailure(false);
@@ -168,6 +179,101 @@
         }
     }
 
+    @Test
+    public void editTextVisibility() throws Throwable {
+        final int spanCount = 3;
+        final int itemCount = 100;
+
+        imeCleanUp.setContainerView(getActivity().getContainer());
+        RecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+        GridEditTextAdapter editTextAdapter = new GridEditTextAdapter(itemCount) {
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+                // Good to have colors for debugging
+                StateListDrawable stl = new StateListDrawable();
+                stl.addState(new int[]{android.R.attr.state_focused},
+                        new ColorDrawable(Color.RED));
+                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+                //noinspection deprecation using this for kitkat tests
+                testViewHolder.itemView.setBackgroundDrawable(stl);
+                return testViewHolder;
+            }
+        };
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+            }
+        });
+
+        recyclerView.setLayoutParams(
+                new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+
+        Config config = new Config(spanCount, itemCount);
+        mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
+                config.mReverseLayout);
+        editTextAdapter.assignSpanSizeLookup(mGlm);
+        recyclerView.setAdapter(editTextAdapter);
+        recyclerView.setLayoutManager(mGlm);
+        waitForFirstLayout(recyclerView);
+
+        // First focus on the last fully visible EditText located at span index #1.
+        View toFocus = findLastFullyVisibleChild(mRecyclerView);
+        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        focusIndex = (focusIndex / spanCount) * spanCount + 1;
+        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+        assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+
+        final int heightBeforeImeOpen = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen;
+            }
+        });
+
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Close IME
+        final int heightBeforeImeClose = mRecyclerView.getHeight();
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to close
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Now focus on the first fully visible EditText located at the last span index.
+        toFocus = findFirstFullyVisibleChild(mRecyclerView);
+        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        focusIndex = (focusIndex / spanCount) * spanCount + (spanCount - 1);
+        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+    }
 
     @Test
     public void topUnfocusableViewsVisibility() throws Throwable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index a2db640..91d0dbf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -34,13 +37,19 @@
 import android.os.Build;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
+import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v7.util.ImeCleanUpTestRule;
+import android.support.v7.util.TouchUtils;
 import android.util.Log;
 import android.util.StateSet;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -60,6 +69,111 @@
 @LargeTest
 public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
 
+    @Rule
+    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
+
+    @Test
+    public void editTextVisibility() throws Throwable {
+
+        // Simulating a scenario where an EditText is tapped (which will receive focus).
+        // The soft keyboard that's opened overlaps the focused EditText which will shrink RV's
+        // padded bounded area. LLM should still lay out the focused EditText so that it becomes
+        // visible above the soft keyboard.
+        // The condition for this test is setting RV's height to a non-exact height, so that measure
+        // is called twice (once with the larger height and another time with smaller height when
+        // the keyboard shows up). To ensure this resizing of RV, SOFT_INPUT_ADJUST_RESIZE is set.
+        imeCleanUp.setContainerView(getActivity().getContainer());
+        final LinearLayout container = new LinearLayout(getActivity());
+        container.setOrientation(LinearLayout.VERTICAL);
+        container.setLayoutParams(
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
+                        .LayoutParams.MATCH_PARENT));
+
+        final EditTextAdapter editTextAdapter = new EditTextAdapter(50);
+
+        mRecyclerView = inflateWrappedRV();
+        ViewGroup.LayoutParams lp = mRecyclerView.getLayoutParams();
+        lp.height = WRAP_CONTENT;
+        lp.width = MATCH_PARENT;
+
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setAdapter(editTextAdapter);
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+
+        container.addView(mRecyclerView);
+
+        mLayoutManager.expectLayouts(1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().getContainer().addView(container);
+            }
+        });
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+            }
+        });
+
+        // First focus on the last fully visible EditText.
+        View toFocus = findLastFullyVisibleChild(mRecyclerView);
+        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+
+        final int heightBeforeImeOpen = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+       // Wait for IME to pop up.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen;
+            }
+        });
+
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        // Testing for partial visibility instead of full visibility since TextView calls
+        // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
+        // containing the content area. This rect is guaranteed to be fully visible whereas a
+        // portion of TextView could be out of bounds.
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Close IME
+        final int heightBeforeImeClose = mRecyclerView.getHeight();
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to close
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            }
+        });
+        assertThat("Child at position " + focusIndex + " should be focused",
+                toFocus.hasFocus(), is(true));
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+
+        // Now focus on the first fully visible EditText.
+        toFocus = findFirstFullyVisibleChild(mRecyclerView);
+        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
+        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+        getInstrumentation().waitForIdleSync();
+        // Wait for IME to pop up
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            }
+        });
+        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(mRecyclerView, toFocus));
+    }
+
     @Test
     public void topUnfocusableViewsVisibility() throws Throwable {
         // The maximum number of child views that can be visible at any time.