/* * Copyright (C) 2015 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 android.support.v4.view; import android.support.annotation.Nullable; import android.support.test.espresso.Espresso; import android.support.test.espresso.IdlingResource; import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; import android.support.test.espresso.action.CoordinatesProvider; import android.support.test.espresso.action.GeneralClickAction; import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Tap; import android.view.View; import android.widget.TextView; import org.hamcrest.Matcher; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; public class ViewPagerActions { /** * View pager listener that serves as Espresso's {@link IdlingResource} and notifies the * registered callback when the view pager gets to STATE_IDLE state. */ private static class CustomViewPagerListener implements ViewPager.OnPageChangeListener, IdlingResource { private int mCurrState = ViewPager.SCROLL_STATE_IDLE; @Nullable private IdlingResource.ResourceCallback mCallback; private boolean mNeedsIdle = false; @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { mCallback = resourceCallback; } @Override public String getName() { return "View pager listener"; } @Override public boolean isIdleNow() { if (!mNeedsIdle) { return true; } else { return mCurrState == ViewPager.SCROLL_STATE_IDLE; } } @Override public void onPageSelected(int position) { if (mCurrState == ViewPager.SCROLL_STATE_IDLE) { if (mCallback != null) { mCallback.onTransitionToIdle(); } } } @Override public void onPageScrollStateChanged(int state) { mCurrState = state; if (mCurrState == ViewPager.SCROLL_STATE_IDLE) { if (mCallback != null) { mCallback.onTransitionToIdle(); } } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } } private abstract static class WrappedViewAction implements ViewAction { } public static ViewAction wrap(final ViewAction baseAction) { if (baseAction instanceof WrappedViewAction) { throw new IllegalArgumentException("Don't wrap an already wrapped action"); } return new WrappedViewAction() { @Override public Matcher getConstraints() { return baseAction.getConstraints(); } @Override public String getDescription() { return baseAction.getDescription(); } @Override public final void perform(UiController uiController, View view) { final ViewPager viewPager = (ViewPager) view; // Add a custom tracker listener final CustomViewPagerListener customListener = new CustomViewPagerListener(); viewPager.addOnPageChangeListener(customListener); // Note that we're running the following block in a try-finally construct. This // is needed since some of the wrapped actions are going to throw (expected) // exceptions. If that happens, we still need to clean up after ourselves to // leave the system (Espesso) in a good state. try { // Register our listener as idling resource so that Espresso waits until the // wrapped action results in the view pager getting to the STATE_IDLE state Espresso.registerIdlingResources(customListener); baseAction.perform(uiController, view); customListener.mNeedsIdle = true; uiController.loopMainThreadUntilIdle(); customListener.mNeedsIdle = false; } finally { // Unregister our idling resource Espresso.unregisterIdlingResources(customListener); // And remove our tracker listener from ViewPager viewPager.removeOnPageChangeListener(customListener); } } }; } /** * Moves ViewPager to the right by one page. */ public static ViewAction scrollRight(final boolean smoothScroll) { return wrap(new ViewAction() { @Override public Matcher getConstraints() { return isDisplayingAtLeast(90); } @Override public String getDescription() { return "ViewPager move one page to the right"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; int current = viewPager.getCurrentItem(); viewPager.setCurrentItem(current + 1, smoothScroll); uiController.loopMainThreadUntilIdle(); } }); } /** * Moves ViewPager to the left by one page. */ public static ViewAction scrollLeft(final boolean smoothScroll) { return wrap(new ViewAction() { @Override public Matcher getConstraints() { return isDisplayingAtLeast(90); } @Override public String getDescription() { return "ViewPager move one page to the left"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; int current = viewPager.getCurrentItem(); viewPager.setCurrentItem(current - 1, smoothScroll); uiController.loopMainThreadUntilIdle(); } }); } /** * Moves ViewPager to the last page. */ public static ViewAction scrollToLast(final boolean smoothScroll) { return wrap(new ViewAction() { @Override public Matcher getConstraints() { return isDisplayingAtLeast(90); } @Override public String getDescription() { return "ViewPager move to last page"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; int size = viewPager.getAdapter().getCount(); if (size > 0) { viewPager.setCurrentItem(size - 1, smoothScroll); } uiController.loopMainThreadUntilIdle(); } }); } /** * Moves ViewPager to the first page. */ public static ViewAction scrollToFirst(final boolean smoothScroll) { return wrap(new ViewAction() { @Override public Matcher getConstraints() { return isDisplayingAtLeast(90); } @Override public String getDescription() { return "ViewPager move to first page"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; int size = viewPager.getAdapter().getCount(); if (size > 0) { viewPager.setCurrentItem(0, smoothScroll); } uiController.loopMainThreadUntilIdle(); } }); } /** * Moves ViewPager to specific page. */ public static ViewAction scrollToPage(final int page, final boolean smoothScroll) { return wrap(new ViewAction() { @Override public Matcher getConstraints() { return isDisplayingAtLeast(90); } @Override public String getDescription() { return "ViewPager move to page"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; viewPager.setCurrentItem(page, smoothScroll); uiController.loopMainThreadUntilIdle(); } }); } /** * Moves ViewPager to specific page. */ public static ViewAction setAdapter(final PagerAdapter adapter) { return new ViewAction() { @Override public Matcher getConstraints() { return isAssignableFrom(ViewPager.class); } @Override public String getDescription() { return "ViewPager set adapter"; } @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); ViewPager viewPager = (ViewPager) view; viewPager.setAdapter(adapter); uiController.loopMainThreadUntilIdle(); } }; } /** * Clicks between two titles in a ViewPager title strip */ public static ViewAction clickBetweenTwoTitles(final String title1, final String title2) { return new GeneralClickAction( Tap.SINGLE, new CoordinatesProvider() { @Override public float[] calculateCoordinates(View view) { PagerTitleStrip pagerStrip = (PagerTitleStrip) view; // Get the screen position of the pager strip final int[] viewScreenPosition = new int[2]; pagerStrip.getLocationOnScreen(viewScreenPosition); // Get the left / right of the first title int title1Left = 0, title1Right = 0, title2Left = 0, title2Right = 0; final int childCount = pagerStrip.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = pagerStrip.getChildAt(i); if (child instanceof TextView) { final TextView textViewChild = (TextView) child; final CharSequence childText = textViewChild.getText(); if (title1.equals(childText)) { title1Left = textViewChild.getLeft(); title1Right = textViewChild.getRight(); } else if (title2.equals(childText)) { title2Left = textViewChild.getLeft(); title2Right = textViewChild.getRight(); } } } if (title1Right < title2Left) { // Title 1 is to the left of title 2 return new float[] { viewScreenPosition[0] + (title1Right + title2Left) / 2, viewScreenPosition[1] + pagerStrip.getHeight() / 2 }; } else { // The assumption here is that PagerTitleStrip prevents titles // from overlapping, so if we get here it means that title 1 // is to the right of title 2 return new float[] { viewScreenPosition[0] + (title2Right + title1Left) / 2, viewScreenPosition[1] + pagerStrip.getHeight() / 2 }; } } }, Press.FINGER); } }