Fail focus search if focused view disappears during focus search

This CL fixes an edge case where the last focused view disappears
during a focusSearch and we need to call focus search failure.

Calling super potentially crashes the app and calling the LayoutManager
invalidates the API. For now, best seems to just fail the focus
search. Later, we can get more clever and try to pick a view
in a position around the previously focused view.

Bug: 27837873
Change-Id: Id8d5e8ea2e3ce40982e0055b0f45777ef8b2b055
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index e7bff81..cd318e8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -2133,6 +2133,11 @@
             }
             if (needsFocusFailureLayout) {
                 consumePendingUpdateOperations();
+                final View focusedItemView = findContainingItemView(focused);
+                if (focusedItemView == null) {
+                    // panic, focused view is not a child anymore, cannot call super.
+                    return null;
+                }
                 eatRequestLayout();
                 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
                 resumeRequestLayout(false);
@@ -2142,6 +2147,11 @@
             result = ff.findNextFocus(this, focused, direction);
             if (result == null && canRunFocusFailure) {
                 consumePendingUpdateOperations();
+                final View focusedItemView = findContainingItemView(focused);
+                if (focusedItemView == null) {
+                    // panic, focused view is not a child anymore, cannot call super.
+                    return null;
+                }
                 eatRequestLayout();
                 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
                 resumeRequestLayout(false);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 75a3efd..4f71056 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -608,6 +608,39 @@
     }
 
     @Test
+    public void testFocusSearchWithRemovedFocusedItem() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setItemAnimator(null);
+        TestLayoutManager tlm = new LayoutAllLayoutManager();
+        recyclerView.setLayoutManager(tlm);
+        final TestAdapter adapter = new TestAdapter(10) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder, int position) {
+                super.onBindViewHolder(holder, position);
+                holder.itemView.setFocusable(true);
+                holder.itemView.setFocusableInTouchMode(true);
+            }
+        };
+        recyclerView.setAdapter(adapter);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(1);
+        final RecyclerView.ViewHolder toFocus = recyclerView.findViewHolderForAdapterPosition(9);
+        requestFocus(toFocus.itemView, true);
+        assertThat("test sanity", toFocus.itemView.hasFocus(), is(true));
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                adapter.mItems.remove(9);
+                adapter.notifyItemRemoved(9);
+                recyclerView.focusSearch(toFocus.itemView, View.FOCUS_DOWN);
+            }
+        });
+        checkForMainThreadException();
+    }
+
+
+    @Test
     public void  testFocusSearchFailFrozen() throws Throwable {
         RecyclerView recyclerView = new RecyclerView(getActivity());
         final CountDownLatch focusLatch = new CountDownLatch(1);
@@ -4091,6 +4124,7 @@
     public class LayoutAllLayoutManager extends TestLayoutManager {
         @Override
         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            detachAndScrapAttachedViews(recycler);
             layoutRange(recycler, 0, state.getItemCount());
             layoutLatch.countDown();
         }