blob: 7e175dd4ea68e49d415a109a4e09748e99497497 [file] [log] [blame]
/*
* 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<View> 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);
}
}
};
}
/**
* Scrolls <code>ViewPager</code> using arrowScroll method in a specified direction.
*/
public static ViewAction arrowScroll(final int direction) {
return wrap(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isDisplayingAtLeast(90);
}
@Override
public String getDescription() {
return "ViewPager arrow scroll in direction: " + direction;
}
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle();
ViewPager viewPager = (ViewPager) view;
viewPager.arrowScroll(direction);
uiController.loopMainThreadUntilIdle();
}
});
}
/**
* Moves <code>ViewPager</code> to the right by one page.
*/
public static ViewAction scrollRight(final boolean smoothScroll) {
return wrap(new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> to the left by one page.
*/
public static ViewAction scrollLeft(final boolean smoothScroll) {
return wrap(new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> to the last page.
*/
public static ViewAction scrollToLast(final boolean smoothScroll) {
return wrap(new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> to the first page.
*/
public static ViewAction scrollToFirst(final boolean smoothScroll) {
return wrap(new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> to specific page.
*/
public static ViewAction scrollToPage(final int page, final boolean smoothScroll) {
return wrap(new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> to specific page.
*/
public static ViewAction setAdapter(final PagerAdapter adapter) {
return new ViewAction() {
@Override
public Matcher<View> 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 <code>ViewPager</code> 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);
}
}