content: /*
   * Copyright (C) 2016 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 androidx.test.espresso.action;
  
  import static androidx.test.internal.util.Checks.checkNotNull;
  import static androidx.test.internal.util.Checks.checkState;
  
  import android.view.View;
  import android.widget.ViewFlipper;
  import androidx.test.espresso.PerformException;
  import androidx.test.espresso.UiController;
  import androidx.test.espresso.ViewAction;
  import androidx.test.espresso.util.HumanReadables;
  import java.util.Locale;
  import org.hamcrest.Matcher;
  import org.hamcrest.StringDescription;
  
  /**
   * Enables performing a given action on a view until it reaches desired state matched by given View
   * matcher. This action is useful in the scenarios where performing the action repeatedly on a view
   * changes its state at runtime. For example, if there is a {@link ViewFlipper} on which user can
   * swipe through the views and it automatically flips between each child at a regular interval, it
   * is not always certain which of the child is displayed at a given point of time. In this case, in
   * order to perform click on child no. 4 (assuming child no. 4 contains a text "Child 4"), repeat
   * action can be used as follows:
   *
   * <p>
   *
   * <blockquote>
   *
   * <pre>{@code
   * int maxAttempts=10;
   * onView(withId(R.id.my_pager))
   *            .perform(repeatedlyUntil(swipeUp(), hasDescendant(withText("Child 4")), maxAttempts),
   *            click());
   * }</pre>
   *
   * </blockquote>
   */
  public final class RepeatActionUntilViewState implements ViewAction {
  
    private final ViewAction mAction;
    private final Matcher<View> mDesiredStateMatcher;
    private final int mMaxAttempts;
  
    protected RepeatActionUntilViewState(
        ViewAction action, Matcher<View> desiredStateMatcher, int maxAttempts) {
      checkNotNull(action);
      checkNotNull(desiredStateMatcher);
      checkState(maxAttempts > 1, "maxAttempts should be greater than 1");
      this.mAction = action;
      this.mDesiredStateMatcher = desiredStateMatcher;
      this.mMaxAttempts = maxAttempts;
    }
  
    @Override
    public Matcher<View> getConstraints() {
      return mAction.getConstraints();
    }
  
    @Override
    public String getDescription() {
      StringDescription stringDescription = new StringDescription();
      mDesiredStateMatcher.describeTo(stringDescription);
      return String.format(Locale.ROOT, "%s until: %s", mAction.getDescription(), stringDescription);
    }
  
    @Override
    public void perform(UiController uiController, View view) {
      int noOfAttempts = 1;
      for (; !mDesiredStateMatcher.matches(view) && noOfAttempts <= mMaxAttempts; noOfAttempts++) {
        mAction.perform(uiController, view);
        uiController.loopMainThreadUntilIdle();
      }
      if (noOfAttempts > mMaxAttempts) {
        throw new PerformException.Builder()
            .withActionDescription(this.getDescription())
            .withViewDescription(HumanReadables.describe(view))
            .withCause(
                new RuntimeException(
                    String.format(
                        Locale.ROOT, "Failed to achieve view state after %d attempts", mMaxAttempts)))
            .build();
      }
    }
  }
  