| /* |
| * Copyright 2018 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 androidx.recyclerview.widget; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.junit.Assert.assertThat; |
| |
| import android.content.Context; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.TextView; |
| |
| import androidx.core.view.ViewCompat; |
| import androidx.test.core.app.ApplicationProvider; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.rule.ActivityTestRule; |
| |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| @RunWith(AndroidJUnit4.class) |
| @LargeTest |
| public class RecyclerViewFocusTest { |
| |
| private static final int RV_HEIGHT_WIDTH = 200; |
| private static final int ITEM_HEIGHT_WIDTH = 100; |
| |
| private RecyclerView mRecyclerView; |
| private TestLinearLayoutManager mTestLinearLayoutManager; |
| private TestContentView mTestContentView; |
| |
| @Rule |
| public ActivityTestRule<TestContentViewActivity> mActivityRule = |
| new ActivityTestRule<>(TestContentViewActivity.class); |
| |
| @Test |
| public void focusSearch_layoutInterceptsAndReturnsNotNull_valueReturned() throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| View expectedView = new View(mActivityRule.getActivity()); |
| View currentlyFocusedView = mRecyclerView.getChildAt(0); |
| mTestLinearLayoutManager.mOnInterceptFocusSearchReturnValue = expectedView; |
| |
| View actualView = mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD); |
| |
| assertThat(actualView, is(equalTo(expectedView))); |
| } |
| |
| @Test |
| public void focusSearch_noAdapter_onFocusSearchFailedNotCalled() throws Throwable { |
| setupRecyclerView(false, RecyclerView.VERTICAL, true); |
| View currentlyFocusedView = mRecyclerView.getChildAt(1); |
| |
| mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD); |
| |
| assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false)); |
| } |
| |
| @Test |
| public void focusSearch_layoutSuppressed_onFocusSearchFailedNotCalled() throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| mRecyclerView.suppressLayout(true); |
| View currentlyFocusedView = mRecyclerView.getChildAt(1); |
| |
| mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD); |
| |
| assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false)); |
| } |
| |
| @Test |
| public void focusSearch_focusedViewNull_onFocusSearchFailedNotCalled() throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| mRecyclerView.focusSearch(null, View.FOCUS_FORWARD); |
| assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false)); |
| } |
| |
| /* |
| Failures, null is returned |
| Tests to verify when onFocusSearchFailed is called. |
| */ |
| |
| @Test |
| public void focusSearch_verticalAndHasChildInDirection_findsCorrectChild() throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_DOWN, 0, 1); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_UP, 1, 0); |
| } |
| |
| @Test |
| public void focusSearch_horizontalAndHasChildInDirection_findsCorrectChild() throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, true); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_RIGHT, 0, 1); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_LEFT, 1, 0); |
| } |
| |
| @Test |
| public void focusSearch_horizontalRtlAndHasChildInDirection_findsCorrectChild() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, false); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_RIGHT, 1, 0); |
| focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_LEFT, 0, 1); |
| } |
| |
| @Test |
| public void focusSearch_verticalAndHasChildInDirection_doesNotCallOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_FORWARD, 0); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_BACKWARD, 1); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_DOWN, 0); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_UP, 1); |
| } |
| |
| @Test |
| public void focusSearch_horizontalAndHasChildInDirection_doesNotCallOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, true); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_FORWARD, 0); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_BACKWARD, 1); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_RIGHT, 0); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_LEFT, 1); |
| } |
| |
| @Test |
| public void focusSearch_horizontalRtlAndHasChildInDirection_doesNotCallOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, false); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_FORWARD, 0); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_BACKWARD, 1); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_RIGHT, 1); |
| focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| View.FOCUS_LEFT, 0); |
| } |
| |
| @Test |
| public void focusSearch_verticalAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.VERTICAL, true); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_DOWN, 1); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_UP, 0); |
| } |
| |
| @Test |
| public void focusSearch_horizontalAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, true); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_RIGHT, 1); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_LEFT, 0); |
| } |
| |
| @Test |
| public void focusSearch_horizontalRtlAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed() |
| throws Throwable { |
| setupRecyclerView(true, RecyclerView.HORIZONTAL, false); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_RIGHT, 0); |
| focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_LEFT, 1); |
| } |
| |
| private void focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(int direction, |
| int startingChild, int expectedChild) { |
| View currentlyFocusedView = mRecyclerView.getChildAt(startingChild); |
| View expectedResult = mRecyclerView.getChildAt(expectedChild); |
| |
| View actualResult = mRecyclerView.focusSearch(currentlyFocusedView, direction); |
| |
| assertThat(actualResult, is(equalTo(expectedResult))); |
| } |
| |
| private void focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled( |
| int direction, int startingChild) { |
| mTestLinearLayoutManager.mOnFocusSearchFailedCalled = false; |
| View currentlyFocusedView = mRecyclerView.getChildAt(startingChild); |
| |
| mRecyclerView.focusSearch(currentlyFocusedView, direction); |
| |
| assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false)); |
| } |
| |
| private void focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(int direction, |
| int startingChild) { |
| mTestLinearLayoutManager.mOnFocusSearchFailedCalled = false; |
| View currentlyFocusedView = mRecyclerView.getChildAt(startingChild); |
| mRecyclerView.focusSearch(currentlyFocusedView, direction); |
| assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(true)); |
| } |
| |
| private void setupRecyclerView(final boolean hasAdapter, int orientation, boolean ltr) |
| throws Throwable { |
| final TestContentViewActivity testContentViewActivity = mActivityRule.getActivity(); |
| mTestContentView = testContentViewActivity.getContentView(); |
| |
| mTestLinearLayoutManager = new TestLinearLayoutManager(testContentViewActivity, |
| orientation, false); |
| |
| mRecyclerView = new RecyclerView(ApplicationProvider.getApplicationContext()); |
| if (!ltr) { |
| ViewCompat.setLayoutDirection(mRecyclerView, View.LAYOUT_DIRECTION_RTL); |
| } |
| mRecyclerView.setBackgroundColor(0xFFFF0000); |
| mRecyclerView.setLayoutParams( |
| new TestContentView.LayoutParams(RV_HEIGHT_WIDTH, RV_HEIGHT_WIDTH)); |
| mRecyclerView.setLayoutManager(mTestLinearLayoutManager); |
| |
| if (hasAdapter) { |
| mRecyclerView.setAdapter( |
| new TestAdapter(100, ITEM_HEIGHT_WIDTH, ITEM_HEIGHT_WIDTH)); |
| } |
| |
| mTestContentView.expectLayouts(1); |
| mActivityRule.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| mTestContentView.addView(mRecyclerView); |
| } |
| }); |
| mTestContentView.awaitLayouts(2); |
| } |
| |
| private class TestLinearLayoutManager extends LinearLayoutManager { |
| |
| boolean mOnFocusSearchFailedCalled = false; |
| View mOnInterceptFocusSearchReturnValue; |
| View mViewToReturnFromonFocusSearchFailed; |
| |
| TestLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { |
| super(context, orientation, reverseLayout); |
| } |
| |
| @Override |
| public View onInterceptFocusSearch(View focused, int direction) { |
| return mOnInterceptFocusSearchReturnValue; |
| } |
| |
| @Override |
| public View onFocusSearchFailed(View focused, int direction, |
| RecyclerView.Recycler recycler, RecyclerView.State state) { |
| mOnFocusSearchFailedCalled = true; |
| return mViewToReturnFromonFocusSearchFailed; |
| } |
| } |
| |
| private class TestAdapter extends RecyclerView.Adapter<TestViewHolder> { |
| |
| private int mItemCount; |
| private int mItemLayoutWidth; |
| private int mItemLayoutHeight; |
| |
| TestAdapter(int itemCount, int itemLayoutWidth, int itemLayoutHeight) { |
| mItemCount = itemCount; |
| mItemLayoutWidth = itemLayoutWidth; |
| mItemLayoutHeight = itemLayoutHeight; |
| } |
| |
| @Override |
| public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| TextView textView = new TextView(parent.getContext()); |
| textView.setLayoutParams( |
| new ViewGroup.LayoutParams(mItemLayoutWidth, mItemLayoutHeight)); |
| textView.setFocusableInTouchMode(true); |
| return new TestViewHolder(textView); |
| } |
| |
| @Override |
| public void onBindViewHolder(TestViewHolder holder, int position) { |
| ((TextView) holder.itemView).setText("Position: " + position); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mItemCount; |
| } |
| } |
| |
| private class TestViewHolder extends RecyclerView.ViewHolder { |
| |
| TestViewHolder(View itemView) { |
| super(itemView); |
| } |
| } |
| } |