androidx.test.espresso.PerformException Java Examples

The following examples show how to use androidx.test.espresso.PerformException. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: CheckAssertionAction.java    From EspressoDescendantActions with Apache License 2.0 6 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
    if (viewAssertion == null) {
        throw new NullPointerException("View assertion is null");
    }

    try {
        viewAssertion.check(view, null);
    }
    catch (Throwable e) {
        throw new PerformException.Builder()
                .withActionDescription(getDescription())
                .withViewDescription(HumanReadables.describe(view))
                .withCause(e)
                .build();
    }
}
 
Example #2
Source File: RepeatActionUntilViewState.java    From android-test with Apache License 2.0 6 votes vote down vote up
@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();
  }
}
 
Example #3
Source File: ScrollToAction.java    From android-test with Apache License 2.0 6 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
  if (isDisplayingAtLeast(90).matches(view)) {
    Log.i(TAG, "View is already displayed. Returning.");
    return;
  }
  Rect rect = new Rect();
  view.getDrawingRect(rect);
  if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
    Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
  }
  uiController.loopMainThreadUntilIdle();
  if (!isDisplayingAtLeast(90).matches(view)) {
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(
            new RuntimeException(
                "Scrolling to view was attempted, but the view is not displayed"))
        .build();
  }
}
 
Example #4
Source File: CloseKeyboardAction.java    From android-test with Apache License 2.0 6 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
  // Retry in case of timeout exception to avoid flakiness in IMM.
  for (int i = 0; i < NUM_RETRIES; i++) {
    try {
      tryToCloseKeyboard(view, uiController);
      return;
    } catch (TimeoutException te) {
      Log.w(TAG, "Caught timeout exception. Retrying.");
      if (i == 2) {
        throw new PerformException.Builder()
            .withActionDescription(this.getDescription())
            .withViewDescription(HumanReadables.describe(view))
            .withCause(te)
            .build();
      }
    }
  }
}
 
Example #5
Source File: RepeatActionUntilViewStateIntegrationTest.java    From android-test with Apache License 2.0 6 votes vote down vote up
@Test
public void performingActionOnViewWithUnreachableViewStateFailsAfterGivenNoOfAttempts() {
  final int maxAttempts = 2;
  expectedException.expect(
      new CustomTypeSafeMatcher<PerformException>(
          "PerformException " + "with expected cause and action description") {
        @Override
        protected boolean matchesSafely(PerformException performException) {

          return performException
                  .getCause()
                  .getMessage()
                  .equals("Failed to achieve view state " + "after " + maxAttempts + " attempts")
              && performException
                  .getActionDescription()
                  .equals("fast swipe until: has descendant: with text: is \"Position #200\"");
        }
      });
  onView(withId(R.id.vertical_pager))
      .check(matches(hasDescendant(withText("Position #0"))))
      .perform(repeatedlyUntil(swipeUp(), hasDescendant(withText("Position #200")), maxAttempts));
}
 
Example #6
Source File: RecyclerViewActions.java    From android-test with Apache License 2.0 6 votes vote down vote up
@Override
public void perform(UiController uiController, View root) {
  RecyclerView recyclerView = (RecyclerView) root;
  try {
    scroller.perform(uiController, root);
    uiController.loopMainThreadUntilIdle();
    // the above scroller has checked bounds, dupes (maybe) and brought the element into screen.
    int max = atPosition == NO_POSITION ? 2 : atPosition + 1;
    int selectIndex = atPosition == NO_POSITION ? 0 : atPosition;
    List<MatchedItem> matchedItems = itemsMatching(recyclerView, viewHolderMatcher, max);
    actionOnItemAtPosition(matchedItems.get(selectIndex).position, viewAction)
        .perform(uiController, root);
    uiController.loopMainThreadUntilIdle();
  } catch (RuntimeException e) {
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(root))
        .withCause(e)
        .build();
  }
}
 
Example #7
Source File: NestedScrollTo.java    From Kore with Apache License 2.0 6 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
    if (isDisplayingAtLeast(90).matches(view)) {
        LogUtils.LOGI(TAG, "View is already displayed. Returning.");
        return;
    }
    Rect rect = new Rect();
    view.getDrawingRect(rect);
    if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
        LogUtils.LOGW(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
    }
    uiController.loopMainThreadUntilIdle();
    if (!isDisplayingAtLeast(90).matches(view)) {
        throw new PerformException.Builder()
                .withActionDescription(this.getDescription())
                .withViewDescription(HumanReadables.describe(view))
                .withCause(new RuntimeException(
                        "Scrolling to view was attempted, but the view is not displayed"))
                .build();
    }
}
 
Example #8
Source File: TypeTextActionTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void typeTextActionPerformFailed() throws InjectEventSecurityException {
  String stringToBeTyped = "Hello!";
  typeTextAction = new TypeTextAction(stringToBeTyped);
  when(mockUiController.injectMotionEvent(isA(MotionEvent.class))).thenReturn(true);
  when(mockUiController.injectString(stringToBeTyped)).thenReturn(false);

  expectedException.expect(PerformException.class);
  expectedException.expectCause(not(instanceOfInjectEventSecurityException()));
  typeTextAction.perform(mockUiController, view);
}
 
Example #9
Source File: TypeTextActionTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void typeTextActionPerformInjectEventSecurityException()
    throws InjectEventSecurityException {
  String stringToBeTyped = "Hello!";
  typeTextAction = new TypeTextAction(stringToBeTyped);
  when(mockUiController.injectMotionEvent(isA(MotionEvent.class))).thenReturn(true);
  when(mockUiController.injectString(stringToBeTyped))
      .thenThrow(new InjectEventSecurityException(""));

  expectedException.expect(PerformException.class);
  expectedException.expectCause(instanceOfInjectEventSecurityException());
  typeTextAction.perform(mockUiController, view);
}
 
Example #10
Source File: OpenLinkActionTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testOpenLink_NoLinkFound() {
  expectedException.expect(PerformException.class);
  expectedException.expectCause(
      new CustomTypeSafeMatcher<Throwable>("message contains has-links=false") {
        @Override
        protected boolean matchesSafely(Throwable throwable) {
          return throwable.getMessage().contains("bacon");
        }
      });
  onView(withId(R.id.spanned)).perform(scrollTo(), openLinkWithText("bacon"));
}
 
Example #11
Source File: OpenLinkActionTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void openLink_TargetViewNotSpanned() {
  expectedException.expect(PerformException.class);
  expectedException.expectCause(
      new CustomTypeSafeMatcher<Throwable>("message contains has-links=false") {
        @Override
        protected boolean matchesSafely(Throwable throwable) {
          return throwable.getMessage().contains("has-links=false");
        }
      });
  onView(withId(R.id.send_title)).perform(scrollTo(), openLinkWithText("altavista.com"));
}
 
Example #12
Source File: OpenLinkAction.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
  TextView textView = (TextView) view;
  String allText = textView.getText().toString();
  URLSpan[] urls = textView.getUrls();
  Spanned spanned = (Spanned) textView.getText();

  // TODO: what if we get more than one hit? For now, take the first one...
  // In the future, we may want to support a way to disambiguate (e.g using text around the link).
  List<String> allLinks = Lists.newArrayList();
  for (URLSpan url : urls) {
    int start = spanned.getSpanStart(url);
    checkState(start != -1, "Unable to get start of text associated with url: " + url);
    int end = spanned.getSpanEnd(url);
    checkState(end != -1, "Unable to get end of text associated with url: " + url);
    String linkText = allText.substring(start, end);
    allLinks.add(linkText);
    if (linkTextMatcher.matches(linkText) && uriMatcher.matches(Uri.parse(url.getURL()))) {
      url.onClick(view);
      return;
    }
  }
  throw new PerformException.Builder()
      .withActionDescription(this.getDescription())
      .withViewDescription(HumanReadables.describe(view))
      .withCause(
          new RuntimeException(
              String.format(
                  Locale.ROOT,
                  "Link with text '%s' and uri '%s' not found. List of links found in this view:"
                      + " %s\n"
                      + "List of uris: %s",
                  linkTextMatcher,
                  uriMatcher,
                  allLinks,
                  Arrays.asList(urls))))
      .build();
}
 
Example #13
Source File: CloseKeyboardAction.java    From android-test with Apache License 2.0 5 votes vote down vote up
private void tryToCloseKeyboard(View view, UiController uiController) throws TimeoutException {
  InputMethodManager imm =
      (InputMethodManager)
          getRootActivity(uiController).getSystemService(Context.INPUT_METHOD_SERVICE);

  CloseKeyboardIdlingResult idlingResult =
      new CloseKeyboardIdlingResult(new Handler(Looper.getMainLooper()));

  IdlingRegistry.getInstance().register(idlingResult);

  try {

    if (!imm.hideSoftInputFromWindow(view.getWindowToken(), 0, idlingResult)) {
      Log.w(TAG, "Attempting to close soft keyboard, while it is not shown.");
      return;
    }
    // set 2 second timeout
    idlingResult.scheduleTimeout(2000);
    uiController.loopMainThreadUntilIdle();
    if (idlingResult.timedOut) {
      throw new TimeoutException("Wait on operation result timed out.");
    }
  } finally {
    IdlingRegistry.getInstance().unregister(idlingResult);
  }

  if (idlingResult.result != InputMethodManager.RESULT_UNCHANGED_HIDDEN
      && idlingResult.result != InputMethodManager.RESULT_HIDDEN) {
    String error =
        "Attempt to close the soft keyboard did not result in soft keyboard to be hidden."
            + " resultCode = "
            + idlingResult.result;
    Log.e(TAG, error);
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(new RuntimeException(error))
        .build();
  }
}
 
Example #14
Source File: EditorActionIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
/**
 * Test only passes if run in isolation. Unless Gradle supports a single instrumentation per test
 * this test is ignored"
 */
@Suppress
@Test
public void pressImeActionButtonOnNonEditorWidget() {
  expectedException.expect(PerformException.class);
  expectedException.expectCause(
      new CustomTypeSafeMatcher<Throwable>("instance of IllegalStateException") {
        @Override
        protected boolean matchesSafely(Throwable throwable) {
          return throwable instanceof IllegalStateException;
        }
      });
  onView(withId(R.id.send_button)).perform(pressImeActionButton());
}
 
Example #15
Source File: DescendantViewAction.java    From EspressoDescendantActions with Apache License 2.0 5 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {

    if (viewAction == null) {
        throw new NullPointerException("View action is null");
    }

    ViewFinder viewFinder = ViewFinderHelper.buildViewFinder(viewMatcher, view);

    View descendantView = viewFinder.getView();

    if (descendantView == null) {
        throw new PerformException.Builder()
                .withActionDescription(getDescription())
                .withViewDescription(HumanReadables.describe(view))
                .withCause(new RuntimeException("Descendant view not found"))
                .build();
    }

    try {
        viewAction.perform(uiController, descendantView);
    }
    catch (Throwable t) {
        throw new PerformException.Builder()
                .withActionDescription(getDescription())
                .withViewDescription(HumanReadables.describe(descendantView))
                .withCause(t)
                .build();
    }
}
 
Example #16
Source File: TypeTextActionIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
/**
 * Test only passes if run in isolation. Unless Gradle supports a single instrumentation per test
 * this test is ignored"
 */
@Test
public void typeTextInFocusedView_constraintBreakage() {
  onView(withId(is(R.id.send_data_to_call_edit_text)))
      .perform(typeText("Hello World How Are You Today? I have alot of text to type."));
  expectedException.expect(PerformException.class);
  onView(withId(is(R.id.edit_text_message)))
      .perform(scrollTo(), typeTextIntoFocusedView("Jolly good!"));
}
 
Example #17
Source File: EditorAction.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
  EditorInfo editorInfo = new EditorInfo();
  InputConnection inputConnection = view.onCreateInputConnection(editorInfo);
  if (inputConnection == null) {
    throw new PerformException.Builder()
        .withActionDescription(this.toString())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(new IllegalStateException("View does not support input methods"))
        .build();
  }

  int actionId =
      editorInfo.actionId != 0
          ? editorInfo.actionId
          : editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;

  if (actionId == EditorInfo.IME_ACTION_NONE) {
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(new IllegalStateException("No available action on view"))
        .build();
  }

  if (!inputConnection.performEditorAction(actionId)) {
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(
            new RuntimeException(
                String.format(
                    Locale.ROOT,
                    "Failed to perform action %#x. Input connection no longer valid",
                    actionId)))
        .build();
  }
}
 
Example #18
Source File: DefaultFailureHandler.java    From android-test with Apache License 2.0 5 votes vote down vote up
/**
 * When the error is coming from espresso, it is more user friendly to: 1. propagate assertions as
 * assertions 2. swap the stack trace of the error to that of current thread (which will show
 * directly where the actual problem is)
 */
private Throwable getUserFriendlyError(Throwable error, Matcher<View> viewMatcher) {
  if (error instanceof PerformException) {
    StringBuilder sb = new StringBuilder();
    if (!isAnimationAndTransitionDisabled(appContext)) {
      sb.append(
          "Animations or transitions are enabled on the target device.\n"
              + "For more info check: https://developer.android.com/training/testing/espresso/setup#set-up-environment\n\n");
    }
    sb.append(viewMatcher.toString());
    // Re-throw the exception with the viewMatcher (used to locate the view) as the view
    // description (makes the error more readable). The reason we do this here: not all creators
    // of PerformException have access to the viewMatcher.
    throw new PerformException.Builder()
        .from((PerformException) error)
        .withViewDescription(sb.toString())
        .build();
  }

  if (error instanceof AssertionError) {
    // reports Failure instead of Error.
    // assertThat(...) throws an AssertionFailedError.
    error = new AssertionFailedWithCauseError(error.getMessage(), error);
  }

  error.setStackTrace(Thread.currentThread().getStackTrace());
  return error;
}
 
Example #19
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testActionOnItem_clickOnItemWithViewHolderMatcherWithPositionOutOfRange() {
  try {
    onView((withId(rvLayoutId)))
        .perform(actionOnHolderItem(new CustomViewHolderMatcher(), click()).atPosition(100));
    fail("PerformException expected.");
  } catch (PerformException expected) {
  }
}
 
Example #20
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testActionOnItem_clickOnItemWithViewHolderMatcherWithAmbiguousViewError() {
  try {
    onView((withId(rvLayoutId)))
        .perform(actionOnHolderItem(new CustomViewHolderMatcher(), click()));
    fail("PerformException expected.");
  } catch (PerformException expected) {
  }
}
 
Example #21
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testErrorMessages_duplicateViewsInHierarchyThrows() throws Throwable {
  final String targetViewText = ITEM_64;
  initWithDuplicateItems(targetViewText, 5);
  try {
    onView(withId(rvLayoutId)).perform(scrollTo(hasDescendant(withText(targetViewText))));
    fail("PerformException expected!");
  } catch (PerformException expected) {
  }
}
 
Example #22
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testErrorMessages_viewNotInHierarchyThrows() {
  String targetViewText = "Not in hierarchy";
  try {
    onView(withId(rvLayoutId)).perform(scrollTo(hasDescendant(withText(targetViewText))));
    fail("PerformException expected!");
  } catch (PerformException expected) {
  }
}
 
Example #23
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testErrorMessages_onNonRecyclerViewThrows() {
  String targetViewText = "Scrolling a non RV should throw";
  try {
    onView(withId(R.id.rv_view_pager)).perform(scrollTo(hasDescendant(withText(targetViewText))));
    fail("PerformException expected!");
  } catch (PerformException expected) {
  }
}
 
Example #24
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testScrolling_scrollToViewWithViewHolderMatcherWithPositionOutOfRange() {
  try {
    onView((withId(rvLayoutId)))
        .perform(scrollToHolder(new CustomViewHolderMatcher()).atPosition(100));
    fail("PerformException expected.");
  } catch (PerformException expected) {
  }
}
 
Example #25
Source File: RecyclerViewIntegrationTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testScrolling_scrollToViewWithViewHolderMatcherWithAmbiguousViewError() {
  try {
    onView((withId(rvLayoutId))).perform(scrollToHolder(new CustomViewHolderMatcher()));
    fail("PerformException expected.");
  } catch (PerformException expected) {
  }
}
 
Example #26
Source File: RecyclerViewActions.java    From android-test with Apache License 2.0 5 votes vote down vote up
@SuppressWarnings("unchecked")
@Override
public void perform(UiController uiController, View view) {
  RecyclerView recyclerView = (RecyclerView) view;
  try {
    int maxMatches = atPosition == NO_POSITION ? 2 : atPosition + 1;
    int selectIndex = atPosition == NO_POSITION ? 0 : atPosition;
    List<MatchedItem> matchedItems = itemsMatching(recyclerView, viewHolderMatcher, maxMatches);

    if (selectIndex >= matchedItems.size()) {
      throw new RuntimeException(
          String.format(
              "Found %d items matching %s, but position %d was requested.",
              matchedItems.size(), viewHolderMatcher.toString(), atPosition));
    }
    if (atPosition == NO_POSITION && matchedItems.size() == 2) {
      StringBuilder ambiguousViewError = new StringBuilder();
      ambiguousViewError.append(
          String.format("Found more than one sub-view matching %s", viewHolderMatcher));
      for (MatchedItem item : matchedItems) {
        ambiguousViewError.append(item + "\n");
      }
      throw new RuntimeException(ambiguousViewError.toString());
    }
    recyclerView.scrollToPosition(matchedItems.get(selectIndex).position);
    uiController.loopMainThreadUntilIdle();
  } catch (RuntimeException e) {
    throw new PerformException.Builder()
        .withActionDescription(this.getDescription())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(e)
        .build();
  }
}
 
Example #27
Source File: RecyclerViewActions.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Override
public void perform(UiController uiController, View view) {
  RecyclerView recyclerView = (RecyclerView) view;

  new ScrollToPositionViewAction(position).perform(uiController, view);
  uiController.loopMainThreadUntilIdle();

  @SuppressWarnings("unchecked")
  VH viewHolderForPosition = (VH) recyclerView.findViewHolderForAdapterPosition(position);
  if (null == viewHolderForPosition) {
    throw new PerformException.Builder()
        .withActionDescription(this.toString())
        .withViewDescription(HumanReadables.describe(view))
        .withCause(new IllegalStateException("No view holder at position: " + position))
        .build();
  }

  View viewAtPosition = viewHolderForPosition.itemView;
  if (null == viewAtPosition) {
    throw new PerformException.Builder()
        .withActionDescription(this.toString())
        .withViewDescription(HumanReadables.describe(viewAtPosition))
        .withCause(new IllegalStateException("No view at position: " + position))
        .build();
  }

  viewAction.perform(uiController, viewAtPosition);
}
 
Example #28
Source File: CursorAdapterTest.java    From android-test with Apache License 2.0 5 votes vote down vote up
@Test
public void testClickOnColumnNameNotFound() {
  try {
    onData(withRowInt("not_there", 1).withStrictColumnChecks(false)).perform(click());
    fail("Should have thrown PerformException");
  } catch (PerformException expected) {}
}
 
Example #29
Source File: ViewActions.java    From Kore with Apache License 2.0 5 votes vote down vote up
/**
 * ViewAction that waits until view with viewId becomes visible
 * @param viewId Resource identifier of view item that must be checked
 * @param checkStatus called when viewId has been found to check its status. If return value
 *                      is true waitForView will stop, false it will continue until timeout is exceeded
 * @param millis amount of time to wait for view to become visible
 * @return
 */
public static ViewAction waitForView(final int viewId, final CheckStatus checkStatus, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Searches for view with id: " + viewId + " and tests its status using CheckStatus, using timeout " + millis + " ms.";
        }

        @Override
        public void perform(UiController uiController, View view) {
            final long endTime = System.currentTimeMillis() + millis;
            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    if (child.getId() == viewId) {
                        if (checkStatus.check(child)) {
                            return;
                        }
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            } while (System.currentTimeMillis() < endTime);

            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}
 
Example #30
Source File: MotionEvents.java    From android-test with Apache License 2.0 4 votes vote down vote up
public static DownResultHolder sendDown(
    UiController uiController,
    float[] coordinates,
    float[] precision,
    int inputDevice,
    int buttonState) {
  checkNotNull(uiController);
  checkNotNull(coordinates);
  checkNotNull(precision);

  for (int retry = 0; retry < MAX_CLICK_ATTEMPTS; retry++) {
    MotionEvent motionEvent = null;
    try {
      motionEvent = obtainDownEvent(coordinates, precision, inputDevice, buttonState);
      // The down event should be considered a tap if it is long enough to be detected
      // but short enough not to be a long-press. Assume that TapTimeout is set at least
      // twice the detection time for a tap (no need to sleep for the whole TapTimeout since
      // we aren't concerned about scrolling here).
      long downTime = motionEvent.getDownTime();
      long isTapAt = downTime + (ViewConfiguration.getTapTimeout() / 2);

      boolean injectEventSucceeded = uiController.injectMotionEvent(motionEvent);

      while (true) {
        long delayToBeTap = isTapAt - SystemClock.uptimeMillis();
        if (delayToBeTap <= 10) {
          break;
        }
        // Sleep only a fraction of the time, since there may be other events in the UI queue
        // that could cause us to start sleeping late, and then oversleep.
        uiController.loopMainThreadForAtLeast(delayToBeTap / 4);
      }

      boolean longPress = false;
      if (SystemClock.uptimeMillis() > (downTime + ViewConfiguration.getLongPressTimeout())) {
        longPress = true;
        Log.w(TAG, "Overslept and turned a tap into a long press");
      }

      if (!injectEventSucceeded) {
        motionEvent.recycle();
        motionEvent = null;
        continue;
      }

      return new DownResultHolder(motionEvent, longPress);
    } catch (InjectEventSecurityException e) {
      throw new PerformException.Builder()
          .withActionDescription("Send down motion event")
          .withViewDescription("unknown") // likely to be replaced by FailureHandler
          .withCause(e)
          .build();
    }
  }
  throw new PerformException.Builder()
      .withActionDescription(
          String.format(Locale.ROOT, "click (after %s attempts)", MAX_CLICK_ATTEMPTS))
      .withViewDescription("unknown") // likely to be replaced by FailureHandler
      .build();
}