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.viewpager.widget;
     18 
     19 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
     20 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
     21 
     22 import android.support.test.espresso.Espresso;
     23 import android.support.test.espresso.IdlingResource;
     24 import android.support.test.espresso.UiController;
     25 import android.support.test.espresso.ViewAction;
     26 import android.support.test.espresso.action.CoordinatesProvider;
     27 import android.support.test.espresso.action.GeneralClickAction;
     28 import android.support.test.espresso.action.Press;
     29 import android.support.test.espresso.action.Tap;
     30 import android.view.View;
     31 import android.widget.TextView;
     32 
     33 import androidx.annotation.Nullable;
     34 
     35 import org.hamcrest.Matcher;
     36 
     37 public class ViewPagerActions {
     38     /**
     39      * View pager listener that serves as Espresso's {@link IdlingResource} and notifies the
     40      * registered callback when the view pager gets to STATE_IDLE state.
     41      */
     42     private static class CustomViewPagerListener
     43             implements ViewPager.OnPageChangeListener, IdlingResource {
     44         private int mCurrState = ViewPager.SCROLL_STATE_IDLE;
     45 
     46         @Nullable
     47         private IdlingResource.ResourceCallback mCallback;
     48 
     49         private boolean mNeedsIdle = false;
     50 
     51         @Override
     52         public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
     53             mCallback = resourceCallback;
     54         }
     55 
     56         @Override
     57         public String getName() {
     58             return "View pager listener";
     59         }
     60 
     61         @Override
     62         public boolean isIdleNow() {
     63             if (!mNeedsIdle) {
     64                 return true;
     65             } else {
     66                 return mCurrState == ViewPager.SCROLL_STATE_IDLE;
     67             }
     68         }
     69 
     70         @Override
     71         public void onPageSelected(int position) {
     72             if (mCurrState == ViewPager.SCROLL_STATE_IDLE) {
     73                 if (mCallback != null) {
     74                     mCallback.onTransitionToIdle();
     75                 }
     76             }
     77         }
     78 
     79         @Override
     80         public void onPageScrollStateChanged(int state) {
     81             mCurrState = state;
     82             if (mCurrState == ViewPager.SCROLL_STATE_IDLE) {
     83                 if (mCallback != null) {
     84                     mCallback.onTransitionToIdle();
     85                 }
     86             }
     87         }
     88 
     89         @Override
     90         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
     91         }
     92     }
     93 
     94     private abstract static class WrappedViewAction implements ViewAction {
     95     }
     96 
     97     public static ViewAction wrap(final ViewAction baseAction) {
     98         if (baseAction instanceof WrappedViewAction) {
     99             throw new IllegalArgumentException("Don't wrap an already wrapped action");
    100         }
    101 
    102         return new WrappedViewAction() {
    103             @Override
    104             public Matcher<View> getConstraints() {
    105                 return baseAction.getConstraints();
    106             }
    107 
    108             @Override
    109             public String getDescription() {
    110                 return baseAction.getDescription();
    111             }
    112 
    113             @Override
    114             public void perform(UiController uiController, View view) {
    115                 final ViewPager viewPager = (ViewPager) view;
    116                 // Add a custom tracker listener
    117                 final CustomViewPagerListener customListener = new CustomViewPagerListener();
    118                 viewPager.addOnPageChangeListener(customListener);
    119 
    120                 // Note that we're running the following block in a try-finally construct. This
    121                 // is needed since some of the wrapped actions are going to throw (expected)
    122                 // exceptions. If that happens, we still need to clean up after ourselves to
    123                 // leave the system (Espesso) in a good state.
    124                 try {
    125                     // Register our listener as idling resource so that Espresso waits until the
    126                     // wrapped action results in the view pager getting to the STATE_IDLE state
    127                     Espresso.registerIdlingResources(customListener);
    128                     baseAction.perform(uiController, view);
    129                     customListener.mNeedsIdle = true;
    130                     uiController.loopMainThreadUntilIdle();
    131                     customListener.mNeedsIdle = false;
    132                 } finally {
    133                     // Unregister our idling resource
    134                     Espresso.unregisterIdlingResources(customListener);
    135                     // And remove our tracker listener from ViewPager
    136                     viewPager.removeOnPageChangeListener(customListener);
    137                 }
    138             }
    139         };
    140     }
    141 
    142     /**
    143      * Scrolls <code>ViewPager</code> using arrowScroll method in a specified direction.
    144      */
    145     public static ViewAction arrowScroll(final int direction) {
    146         return wrap(new ViewAction() {
    147             @Override
    148             public Matcher<View> getConstraints() {
    149                 return isDisplayingAtLeast(90);
    150             }
    151 
    152             @Override
    153             public String getDescription() {
    154                 return "ViewPager arrow scroll in direction: " + direction;
    155             }
    156 
    157             @Override
    158             public void perform(UiController uiController, View view) {
    159                 uiController.loopMainThreadUntilIdle();
    160 
    161                 ViewPager viewPager = (ViewPager) view;
    162                 viewPager.arrowScroll(direction);
    163                 uiController.loopMainThreadUntilIdle();
    164             }
    165         });
    166     }
    167 
    168     /**
    169      * Moves <code>ViewPager</code> to the right by one page.
    170      */
    171     public static ViewAction scrollRight(final boolean smoothScroll) {
    172         return wrap(new ViewAction() {
    173             @Override
    174             public Matcher<View> getConstraints() {
    175                 return isDisplayingAtLeast(90);
    176             }
    177 
    178             @Override
    179             public String getDescription() {
    180                 return "ViewPager move one page to the right";
    181             }
    182 
    183             @Override
    184             public void perform(UiController uiController, View view) {
    185                 uiController.loopMainThreadUntilIdle();
    186 
    187                 ViewPager viewPager = (ViewPager) view;
    188                 int current = viewPager.getCurrentItem();
    189                 viewPager.setCurrentItem(current + 1, smoothScroll);
    190 
    191                 uiController.loopMainThreadUntilIdle();
    192             }
    193         });
    194     }
    195 
    196     /**
    197      * Moves <code>ViewPager</code> to the left by one page.
    198      */
    199     public static ViewAction scrollLeft(final boolean smoothScroll) {
    200         return wrap(new ViewAction() {
    201             @Override
    202             public Matcher<View> getConstraints() {
    203                 return isDisplayingAtLeast(90);
    204             }
    205 
    206             @Override
    207             public String getDescription() {
    208                 return "ViewPager move one page to the left";
    209             }
    210 
    211             @Override
    212             public void perform(UiController uiController, View view) {
    213                 uiController.loopMainThreadUntilIdle();
    214 
    215                 ViewPager viewPager = (ViewPager) view;
    216                 int current = viewPager.getCurrentItem();
    217                 viewPager.setCurrentItem(current - 1, smoothScroll);
    218 
    219                 uiController.loopMainThreadUntilIdle();
    220             }
    221         });
    222     }
    223 
    224     /**
    225      * Moves <code>ViewPager</code> to the last page.
    226      */
    227     public static ViewAction scrollToLast(final boolean smoothScroll) {
    228         return wrap(new ViewAction() {
    229             @Override
    230             public Matcher<View> getConstraints() {
    231                 return isDisplayingAtLeast(90);
    232             }
    233 
    234             @Override
    235             public String getDescription() {
    236                 return "ViewPager move to last page";
    237             }
    238 
    239             @Override
    240             public void perform(UiController uiController, View view) {
    241                 uiController.loopMainThreadUntilIdle();
    242 
    243                 ViewPager viewPager = (ViewPager) view;
    244                 int size = viewPager.getAdapter().getCount();
    245                 if (size > 0) {
    246                     viewPager.setCurrentItem(size - 1, smoothScroll);
    247                 }
    248 
    249                 uiController.loopMainThreadUntilIdle();
    250             }
    251         });
    252     }
    253 
    254     /**
    255      * Moves <code>ViewPager</code> to the first page.
    256      */
    257     public static ViewAction scrollToFirst(final boolean smoothScroll) {
    258         return wrap(new ViewAction() {
    259             @Override
    260             public Matcher<View> getConstraints() {
    261                 return isDisplayingAtLeast(90);
    262             }
    263 
    264             @Override
    265             public String getDescription() {
    266                 return "ViewPager move to first page";
    267             }
    268 
    269             @Override
    270             public void perform(UiController uiController, View view) {
    271                 uiController.loopMainThreadUntilIdle();
    272 
    273                 ViewPager viewPager = (ViewPager) view;
    274                 int size = viewPager.getAdapter().getCount();
    275                 if (size > 0) {
    276                     viewPager.setCurrentItem(0, smoothScroll);
    277                 }
    278 
    279                 uiController.loopMainThreadUntilIdle();
    280             }
    281         });
    282     }
    283 
    284     /**
    285      * Moves <code>ViewPager</code> to specific page.
    286      */
    287     public static ViewAction scrollToPage(final int page, final boolean smoothScroll) {
    288         return wrap(new ViewAction() {
    289             @Override
    290             public Matcher<View> getConstraints() {
    291                 return isDisplayingAtLeast(90);
    292             }
    293 
    294             @Override
    295             public String getDescription() {
    296                 return "ViewPager move to page";
    297             }
    298 
    299             @Override
    300             public void perform(UiController uiController, View view) {
    301                 uiController.loopMainThreadUntilIdle();
    302 
    303                 ViewPager viewPager = (ViewPager) view;
    304                 viewPager.setCurrentItem(page, smoothScroll);
    305 
    306                 uiController.loopMainThreadUntilIdle();
    307             }
    308         });
    309     }
    310 
    311     /**
    312      * Moves <code>ViewPager</code> to specific page.
    313      */
    314     public static ViewAction setAdapter(final PagerAdapter adapter) {
    315         return new ViewAction() {
    316             @Override
    317             public Matcher<View> getConstraints() {
    318                 return isAssignableFrom(ViewPager.class);
    319             }
    320 
    321             @Override
    322             public String getDescription() {
    323                 return "ViewPager set adapter";
    324             }
    325 
    326             @Override
    327             public void perform(UiController uiController, View view) {
    328                 uiController.loopMainThreadUntilIdle();
    329 
    330                 ViewPager viewPager = (ViewPager) view;
    331                 viewPager.setAdapter(adapter);
    332 
    333                 uiController.loopMainThreadUntilIdle();
    334             }
    335         };
    336     }
    337 
    338     /**
    339      * Clicks between two titles in a <code>ViewPager</code> title strip
    340      */
    341     public static ViewAction clickBetweenTwoTitles(final String title1, final String title2) {
    342         return new GeneralClickAction(
    343                 Tap.SINGLE,
    344                 new CoordinatesProvider() {
    345                     @Override
    346                     public float[] calculateCoordinates(View view) {
    347                         PagerTitleStrip pagerStrip = (PagerTitleStrip) view;
    348 
    349                         // Get the screen position of the pager strip
    350                         final int[] viewScreenPosition = new int[2];
    351                         pagerStrip.getLocationOnScreen(viewScreenPosition);
    352 
    353                         // Get the left / right of the first title
    354                         int title1Left = 0, title1Right = 0, title2Left = 0, title2Right = 0;
    355                         final int childCount = pagerStrip.getChildCount();
    356                         for (int i = 0; i < childCount; i++) {
    357                             final View child = pagerStrip.getChildAt(i);
    358                             if (child instanceof TextView) {
    359                                 final TextView textViewChild = (TextView) child;
    360                                 final CharSequence childText = textViewChild.getText();
    361                                 if (title1.equals(childText)) {
    362                                     title1Left = textViewChild.getLeft();
    363                                     title1Right = textViewChild.getRight();
    364                                 } else if (title2.equals(childText)) {
    365                                     title2Left = textViewChild.getLeft();
    366                                     title2Right = textViewChild.getRight();
    367                                 }
    368                             }
    369                         }
    370 
    371                         if (title1Right < title2Left) {
    372                             // Title 1 is to the left of title 2
    373                             return new float[] {
    374                                     viewScreenPosition[0] + (title1Right + title2Left) / 2,
    375                                     viewScreenPosition[1] + pagerStrip.getHeight() / 2 };
    376                         } else {
    377                             // The assumption here is that PagerTitleStrip prevents titles
    378                             // from overlapping, so if we get here it means that title 1
    379                             // is to the right of title 2
    380                             return new float[] {
    381                                     viewScreenPosition[0] + (title2Right + title1Left) / 2,
    382                                     viewScreenPosition[1] + pagerStrip.getHeight() / 2 };
    383                         }
    384                     }
    385                 },
    386                 Press.FINGER);
    387     }
    388 }
    389