Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2017 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.car.widget;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.action.ViewActions.click;
     21 import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
     22 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     23 
     24 import static org.hamcrest.Matchers.allOf;
     25 import static org.junit.Assert.assertEquals;
     26 
     27 import android.content.pm.ActivityInfo;
     28 import android.content.pm.PackageManager;
     29 import android.support.test.espresso.IdlingRegistry;
     30 import android.support.test.espresso.IdlingResource;
     31 import android.support.test.filters.SmallTest;
     32 import android.support.test.filters.Suppress;
     33 import android.support.test.rule.ActivityTestRule;
     34 import android.support.test.runner.AndroidJUnit4;
     35 import androidx.recyclerview.widget.LinearLayoutManager;
     36 import androidx.recyclerview.widget.RecyclerView;
     37 import android.view.LayoutInflater;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.widget.TextView;
     41 
     42 import org.hamcrest.Matcher;
     43 import org.junit.After;
     44 import org.junit.Assume;
     45 import org.junit.Before;
     46 import org.junit.Rule;
     47 import org.junit.Test;
     48 import org.junit.runner.RunWith;
     49 
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Random;
     53 
     54 import androidx.car.test.R;
     55 
     56 /** Unit tests for the ability of the {@link PagedListView} to save state. */
     57 @RunWith(AndroidJUnit4.class)
     58 @SmallTest
     59 public final class PagedListViewSavedStateTest {
     60     /**
     61      * Used by {@link TestAdapter} to calculate ViewHolder height so N items appear in one page of
     62      * {@link PagedListView}. If you need to test behavior under multiple pages, set number of items
     63      * to ITEMS_PER_PAGE * desired_pages.
     64      *
     65      * <p>Actual value does not matter.
     66      */
     67     private static final int ITEMS_PER_PAGE = 5;
     68 
     69     /**
     70      * The total number of items to display in a list. This value just needs to be large enough
     71      * to ensure the scroll bar shows.
     72      */
     73     private static final int TOTAL_ITEMS_IN_LIST = 100;
     74 
     75     private static final int NUM_OF_PAGES = TOTAL_ITEMS_IN_LIST / ITEMS_PER_PAGE;
     76 
     77     @Rule
     78     public ActivityTestRule<PagedListViewSavedStateActivity> mActivityRule =
     79             new ActivityTestRule<>(PagedListViewSavedStateActivity.class);
     80 
     81     private PagedListViewSavedStateActivity mActivity;
     82     private PagedListView mPagedListView1;
     83     private PagedListView mPagedListView2;
     84 
     85     @Before
     86     public void setUp() {
     87         Assume.assumeTrue(isAutoDevice());
     88 
     89         mActivity = mActivityRule.getActivity();
     90         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
     91 
     92         mPagedListView1 = mActivity.findViewById(R.id.paged_list_view_1);
     93         mPagedListView2 = mActivity.findViewById(R.id.paged_list_view_2);
     94 
     95         setUpPagedListView(mPagedListView1);
     96         setUpPagedListView(mPagedListView2);
     97     }
     98 
     99     private boolean isAutoDevice() {
    100         PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
    101         return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
    102     }
    103 
    104     private void setUpPagedListView(PagedListView pagedListView) {
    105         try {
    106             mActivityRule.runOnUiThread(() -> {
    107                 pagedListView.setMaxPages(PagedListView.ItemCap.UNLIMITED);
    108                 pagedListView.setAdapter(new TestAdapter(TOTAL_ITEMS_IN_LIST,
    109                         pagedListView.getMeasuredHeight()));
    110             });
    111         } catch (Throwable throwable) {
    112             throwable.printStackTrace();
    113             throw new RuntimeException(throwable);
    114         }
    115     }
    116 
    117     @After
    118     public void tearDown() {
    119         for (IdlingResource idlingResource : IdlingRegistry.getInstance().getResources()) {
    120             IdlingRegistry.getInstance().unregister(idlingResource);
    121         }
    122     }
    123 
    124     @Suppress
    125     @Test
    126     public void testPagePositionRememberedOnRotation() {
    127         LinearLayoutManager layoutManager1 =
    128                 (LinearLayoutManager) mPagedListView1.getRecyclerView().getLayoutManager();
    129         LinearLayoutManager layoutManager2 =
    130                 (LinearLayoutManager) mPagedListView2.getRecyclerView().getLayoutManager();
    131 
    132         Random random = new Random();
    133         IdlingRegistry.getInstance().register(new PagedListViewScrollingIdlingResource(
    134                 mPagedListView1, mPagedListView2));
    135 
    136         // Add 1 to this random number to ensure it is a value between 1 and NUM_OF_PAGES.
    137         int numOfClicks = 2;
    138         clickPageDownButton(onPagedListView1(), numOfClicks);
    139         int topPositionOfPagedListView1 =
    140                 layoutManager1.findFirstVisibleItemPosition();
    141 
    142         numOfClicks = 3;
    143         clickPageDownButton(onPagedListView2(), numOfClicks);
    144         int topPositionOfPagedListView2 =
    145                 layoutManager2.findFirstVisibleItemPosition();
    146 
    147         // Perform a configuration change by rotating the screen.
    148         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    149         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    150 
    151         // Check that the positions are the same after the change.
    152         assertEquals(topPositionOfPagedListView1,
    153                 layoutManager1.findFirstVisibleItemPosition());
    154         assertEquals(topPositionOfPagedListView2,
    155                 layoutManager2.findFirstVisibleItemPosition());
    156     }
    157 
    158     /** Clicks the page down button on the given PagedListView for the given number of times. */
    159     private void clickPageDownButton(Matcher<View> pagedListView, int times) {
    160         for (int i = 0; i < times; i++) {
    161             onView(allOf(withId(R.id.page_down), pagedListView)).perform(click());
    162         }
    163     }
    164 
    165 
    166     /** Convenience method for checking that a View is on the first PagedListView. */
    167     private Matcher<View> onPagedListView1() {
    168         return isDescendantOfA(withId(R.id.paged_list_view_1));
    169     }
    170 
    171     /** Convenience method for checking that a View is on the second PagedListView. */
    172     private Matcher<View> onPagedListView2() {
    173         return isDescendantOfA(withId(R.id.paged_list_view_2));
    174     }
    175 
    176     private static String getItemText(int index) {
    177         return "Data " + index;
    178     }
    179 
    180     /** An Adapter that ensures that there is {@link #ITEMS_PER_PAGE} displayed. */
    181     private class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
    182             implements PagedListView.ItemCap {
    183         private List<String> mData;
    184         private int mParentHeight;
    185 
    186         TestAdapter(int itemCount, int parentHeight) {
    187             mData = new ArrayList<>();
    188             for (int i = 0; i < itemCount; i++) {
    189                 mData.add(getItemText(i));
    190             }
    191             mParentHeight = parentHeight;
    192         }
    193 
    194         @Override
    195         public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    196             LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    197             return new TestViewHolder(inflater, parent);
    198         }
    199 
    200         @Override
    201         public void onBindViewHolder(TestViewHolder holder, int position) {
    202             // Calculate height for an item so one page fits ITEMS_PER_PAGE items.
    203             int height = (int) Math.floor(mParentHeight / ITEMS_PER_PAGE);
    204             holder.itemView.setMinimumHeight(height);
    205             holder.setText(mData.get(position));
    206         }
    207 
    208         @Override
    209         public int getItemCount() {
    210             return mData.size();
    211         }
    212 
    213         @Override
    214         public void setMaxItems(int maxItems) {
    215             // No-op
    216         }
    217     }
    218 
    219     /** A ViewHolder that holds a View with a TextView. */
    220     private class TestViewHolder extends RecyclerView.ViewHolder {
    221         private TextView mTextView;
    222 
    223         TestViewHolder(LayoutInflater inflater, ViewGroup parent) {
    224             super(inflater.inflate(R.layout.paged_list_item_column_card, parent, false));
    225             mTextView = itemView.findViewById(R.id.text_view);
    226         }
    227 
    228         public void setText(String text) {
    229             mTextView.setText(text);
    230         }
    231     }
    232 
    233     // Registering IdlingResource in @Before method does not work - espresso doesn't actually wait
    234     // for ViewAction to finish. So each method that  clicks on button will need to register their
    235     // own IdlingResource.
    236     private class PagedListViewScrollingIdlingResource implements IdlingResource {
    237         private boolean mIsIdle = true;
    238         private ResourceCallback mResourceCallback;
    239 
    240         PagedListViewScrollingIdlingResource(PagedListView pagedListView1,
    241                 PagedListView pagedListView2) {
    242             // Ensure the IdlingResource waits for both RecyclerViews to finish their movement.
    243             pagedListView1.getRecyclerView().addOnScrollListener(mOnScrollListener);
    244             pagedListView2.getRecyclerView().addOnScrollListener(mOnScrollListener);
    245         }
    246 
    247         @Override
    248         public String getName() {
    249             return PagedListViewScrollingIdlingResource.class.getName();
    250         }
    251 
    252         @Override
    253         public boolean isIdleNow() {
    254             return mIsIdle;
    255         }
    256 
    257         @Override
    258         public void registerIdleTransitionCallback(ResourceCallback callback) {
    259             mResourceCallback = callback;
    260         }
    261 
    262         private final RecyclerView.OnScrollListener mOnScrollListener =
    263                 new RecyclerView.OnScrollListener() {
    264                     @Override
    265                     public void onScrollStateChanged(
    266                             RecyclerView recyclerView, int newState) {
    267                         super.onScrollStateChanged(recyclerView, newState);
    268 
    269                         // Treat dragging as idle, or Espresso will block itself when
    270                         // swiping.
    271                         mIsIdle = (newState == RecyclerView.SCROLL_STATE_IDLE
    272                                 || newState == RecyclerView.SCROLL_STATE_DRAGGING);
    273 
    274                         if (mIsIdle && mResourceCallback != null) {
    275                             mResourceCallback.onTransitionToIdle();
    276                         }
    277                     }
    278 
    279                     @Override
    280                     public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
    281                 };
    282     }
    283 }
    284