Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 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 
     17 package androidx.recyclerview.widget;
     18 
     19 import static org.hamcrest.CoreMatchers.equalTo;
     20 import static org.hamcrest.CoreMatchers.is;
     21 import static org.junit.Assert.assertThat;
     22 
     23 import android.content.Context;
     24 import android.support.test.InstrumentationRegistry;
     25 import android.support.test.filters.LargeTest;
     26 import android.support.test.filters.SdkSuppress;
     27 import android.support.test.rule.ActivityTestRule;
     28 import android.support.test.runner.AndroidJUnit4;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.widget.TextView;
     32 
     33 import org.junit.Rule;
     34 import org.junit.Test;
     35 import org.junit.runner.RunWith;
     36 
     37 @RunWith(AndroidJUnit4.class)
     38 @LargeTest
     39 public class RecyclerViewFocusTest {
     40 
     41     private static final int RV_HEIGHT_WIDTH = 200;
     42     private static final int ITEM_HEIGHT_WIDTH = 100;
     43 
     44     private RecyclerView mRecyclerView;
     45     private TestLinearLayoutManager mTestLinearLayoutManager;
     46     private TestContentView mTestContentView;
     47 
     48     @Rule
     49     public ActivityTestRule<TestContentViewActivity> mActivityRule =
     50             new ActivityTestRule<>(TestContentViewActivity.class);
     51 
     52     @Test
     53     public void focusSearch_layoutInterceptsAndReturnsNotNull_valueReturned() throws Throwable {
     54         setupRecyclerView(true, RecyclerView.VERTICAL, true);
     55         View expectedView = new View(mActivityRule.getActivity());
     56         View currentlyFocusedView = mRecyclerView.getChildAt(0);
     57         mTestLinearLayoutManager.mOnInterceptFocusSearchReturnValue = expectedView;
     58 
     59         View actualView = mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD);
     60 
     61         assertThat(actualView, is(equalTo(expectedView)));
     62     }
     63 
     64     @Test
     65     public void focusSearch_noAdapter_onFocusSearchFailedNotCalled() throws Throwable {
     66         setupRecyclerView(false, RecyclerView.VERTICAL, true);
     67         View currentlyFocusedView = mRecyclerView.getChildAt(1);
     68 
     69         mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD);
     70 
     71         assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false));
     72     }
     73 
     74     @Test
     75     public void focusSearch_layoutFrozen_onFocusSearchFailedNotCalled() throws Throwable {
     76         setupRecyclerView(true, RecyclerView.VERTICAL, true);
     77         mRecyclerView.setLayoutFrozen(true);
     78         View currentlyFocusedView = mRecyclerView.getChildAt(1);
     79 
     80         mRecyclerView.focusSearch(currentlyFocusedView, View.FOCUS_FORWARD);
     81 
     82         assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false));
     83     }
     84 
     85     @Test
     86     public void focusSearch_focusedViewNull_onFocusSearchFailedNotCalled() throws Throwable {
     87         setupRecyclerView(true, RecyclerView.VERTICAL, true);
     88         mRecyclerView.focusSearch(null, View.FOCUS_FORWARD);
     89         assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false));
     90     }
     91 
     92     /*
     93         Failures, null is returned
     94         Tests to verify when onFocusSearchFailed is called.
     95     */
     96 
     97     @Test
     98     public void focusSearch_verticalAndHasChildInDirection_findsCorrectChild() throws Throwable {
     99         setupRecyclerView(true, RecyclerView.VERTICAL, true);
    100         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1);
    101         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0);
    102         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_DOWN, 0, 1);
    103         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_UP, 1, 0);
    104     }
    105 
    106     @Test
    107     public void focusSearch_horizontalAndHasChildInDirection_findsCorrectChild() throws Throwable {
    108         setupRecyclerView(true, RecyclerView.HORIZONTAL, true);
    109         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1);
    110         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0);
    111         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_RIGHT, 0, 1);
    112         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_LEFT, 1, 0);
    113     }
    114 
    115     @Test
    116     @SdkSuppress(minSdkVersion = 17)
    117     public void focusSearch_horizontalRtlAndHasChildInDirection_findsCorrectChild()
    118             throws Throwable {
    119         setupRecyclerView(true, RecyclerView.HORIZONTAL, false);
    120         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_FORWARD, 0, 1);
    121         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_BACKWARD, 1, 0);
    122         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_RIGHT, 1, 0);
    123         focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(View.FOCUS_LEFT, 0, 1);
    124     }
    125 
    126     @Test
    127     public void focusSearch_verticalAndHasChildInDirection_doesNotCallOnFocusSearchFailed()
    128             throws Throwable {
    129         setupRecyclerView(true, RecyclerView.VERTICAL, true);
    130         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    131                 View.FOCUS_FORWARD, 0);
    132         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    133                 View.FOCUS_BACKWARD, 1);
    134         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    135                 View.FOCUS_DOWN, 0);
    136         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    137                 View.FOCUS_UP, 1);
    138     }
    139 
    140     @Test
    141     public void focusSearch_horizontalAndHasChildInDirection_doesNotCallOnFocusSearchFailed()
    142             throws Throwable {
    143         setupRecyclerView(true, RecyclerView.HORIZONTAL, true);
    144         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    145                 View.FOCUS_FORWARD, 0);
    146         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    147                 View.FOCUS_BACKWARD, 1);
    148         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    149                 View.FOCUS_RIGHT, 0);
    150         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    151                 View.FOCUS_LEFT, 1);
    152     }
    153 
    154     @Test
    155     @SdkSuppress(minSdkVersion = 17)
    156     public void focusSearch_horizontalRtlAndHasChildInDirection_doesNotCallOnFocusSearchFailed()
    157             throws Throwable {
    158         setupRecyclerView(true, RecyclerView.HORIZONTAL, false);
    159         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    160                 View.FOCUS_FORWARD, 0);
    161         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    162                 View.FOCUS_BACKWARD, 1);
    163         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    164                 View.FOCUS_RIGHT, 1);
    165         focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    166                 View.FOCUS_LEFT, 0);
    167     }
    168 
    169     @Test
    170     public void focusSearch_verticalAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed()
    171             throws Throwable {
    172         setupRecyclerView(true, RecyclerView.VERTICAL, true);
    173         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1);
    174         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0);
    175         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_DOWN, 1);
    176         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_UP, 0);
    177     }
    178 
    179     @Test
    180     public void focusSearch_horizontalAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed()
    181             throws Throwable {
    182         setupRecyclerView(true, RecyclerView.HORIZONTAL, true);
    183         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1);
    184         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0);
    185         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_RIGHT, 1);
    186         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_LEFT, 0);
    187     }
    188 
    189     @Test
    190     @SdkSuppress(minSdkVersion = 17)
    191     public void focusSearch_horizontalRtlAndDoesNotHaveChildInDirection_callsOnFocusSearchFailed()
    192             throws Throwable {
    193         setupRecyclerView(true, RecyclerView.HORIZONTAL, false);
    194         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_FORWARD, 1);
    195         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_BACKWARD, 0);
    196         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_RIGHT, 0);
    197         focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(View.FOCUS_LEFT, 1);
    198     }
    199 
    200     private void focusSearch_simpleFindFocusSucceeds_returnsCorrectValue(int direction,
    201             int startingChild, int expectedChild) {
    202         View currentlyFocusedView = mRecyclerView.getChildAt(startingChild);
    203         View expectedResult = mRecyclerView.getChildAt(expectedChild);
    204 
    205         View actualResult = mRecyclerView.focusSearch(currentlyFocusedView, direction);
    206 
    207         assertThat(actualResult, is(equalTo(expectedResult)));
    208     }
    209 
    210     private void focusSearch_simpleFindFocusSucceeds_doesNotCallOnFocusSearchFailedCalled(
    211             int direction, int startingChild) {
    212         mTestLinearLayoutManager.mOnFocusSearchFailedCalled = false;
    213         View currentlyFocusedView = mRecyclerView.getChildAt(startingChild);
    214 
    215         mRecyclerView.focusSearch(currentlyFocusedView, direction);
    216 
    217         assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(false));
    218     }
    219 
    220     private void focusSearch_simpleFindFocusFails_callsOnFocusSearchFailed(int direction,
    221             int startingChild) {
    222         mTestLinearLayoutManager.mOnFocusSearchFailedCalled = false;
    223         View currentlyFocusedView = mRecyclerView.getChildAt(startingChild);
    224         mRecyclerView.focusSearch(currentlyFocusedView, direction);
    225         assertThat(mTestLinearLayoutManager.mOnFocusSearchFailedCalled, is(true));
    226     }
    227 
    228     private void setupRecyclerView(final boolean hasAdapter, int orientation, boolean ltr)
    229             throws Throwable {
    230         final TestContentViewActivity testContentViewActivity = mActivityRule.getActivity();
    231         mTestContentView = testContentViewActivity.getContentView();
    232 
    233         mTestLinearLayoutManager = new TestLinearLayoutManager(testContentViewActivity,
    234                 orientation, false);
    235 
    236         mRecyclerView = new RecyclerView(InstrumentationRegistry.getContext());
    237         if (!ltr) {
    238             mRecyclerView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
    239         }
    240         mRecyclerView.setBackgroundColor(0xFFFF0000);
    241         mRecyclerView.setLayoutParams(
    242                 new TestContentView.LayoutParams(RV_HEIGHT_WIDTH, RV_HEIGHT_WIDTH));
    243         mRecyclerView.setLayoutManager(mTestLinearLayoutManager);
    244 
    245         if (hasAdapter) {
    246             mRecyclerView.setAdapter(
    247                     new TestAdapter(100, ITEM_HEIGHT_WIDTH, ITEM_HEIGHT_WIDTH));
    248         }
    249 
    250         mTestContentView.expectLayouts(1);
    251         mActivityRule.runOnUiThread(new Runnable() {
    252             @Override
    253             public void run() {
    254                 mTestContentView.addView(mRecyclerView);
    255             }
    256         });
    257         mTestContentView.awaitLayouts(2);
    258     }
    259 
    260     private class TestLinearLayoutManager extends LinearLayoutManager {
    261 
    262         boolean mOnFocusSearchFailedCalled = false;
    263         View mOnInterceptFocusSearchReturnValue;
    264         View mViewToReturnFromonFocusSearchFailed;
    265 
    266         TestLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    267             super(context, orientation, reverseLayout);
    268         }
    269 
    270         @Override
    271         public View onInterceptFocusSearch(View focused, int direction) {
    272             return mOnInterceptFocusSearchReturnValue;
    273         }
    274 
    275         @Override
    276         public View onFocusSearchFailed(View focused, int focusDirection,
    277                 RecyclerView.Recycler recycler, RecyclerView.State state) {
    278             mOnFocusSearchFailedCalled = true;
    279             return mViewToReturnFromonFocusSearchFailed;
    280         }
    281     }
    282 
    283     private class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
    284 
    285         private int mItemCount;
    286         private int mItemLayoutWidth;
    287         private int mItemLayoutHeight;
    288 
    289         TestAdapter(int itemCount, int itemLayoutWidth, int itemLayoutHeight) {
    290             mItemCount = itemCount;
    291             mItemLayoutWidth = itemLayoutWidth;
    292             mItemLayoutHeight = itemLayoutHeight;
    293         }
    294 
    295         @Override
    296         public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    297             TextView textView = new TextView(parent.getContext());
    298             textView.setLayoutParams(
    299                     new ViewGroup.LayoutParams(mItemLayoutWidth, mItemLayoutHeight));
    300             textView.setFocusableInTouchMode(true);
    301             return new TestViewHolder(textView);
    302         }
    303 
    304         @Override
    305         public void onBindViewHolder(TestViewHolder holder, int position) {
    306             ((TextView) holder.itemView).setText("Position: " + position);
    307         }
    308 
    309         @Override
    310         public int getItemCount() {
    311             return mItemCount;
    312         }
    313     }
    314 
    315     private class TestViewHolder extends RecyclerView.ViewHolder {
    316 
    317         TestViewHolder(View itemView) {
    318             super(itemView);
    319         }
    320     }
    321 }
    322