| /* |
| * 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); |
| } |
| } |