UiController.java

/*
 * Copyright (C) 2014 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;

import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.MotionEvent;
import java.util.Iterator;

/**
 * Provides base-level UI operations (such as injection of {@link MotionEvent}s) that can be used to
 * build user actions such as clicks, scrolls, swipes, etc. This replaces parts of the android
 * Instrumentation class that provides similar functionality. However, it provides a more advanced
 * synchronization mechanism for test actions. The key differentiators are:
 *
 * <ul>
 *   <li>test actions are assumed to be called on the main thread
 *   <li>after a test action is initiated, execution blocks until all messages in the main message
 *       queue have been cleared.
 * </ul>
 */
public interface UiController {
  /**
   * Injects a motion event into the application.
   *
   * @param event the (non-null!) event to inject
   * @return true if the event was injected, false otherwise
   * @throws InjectEventSecurityException if the event couldn't be injected because it would
   *     interact with another application. @Deprecated Please use injectMotionEventSequence this
   *     method will be removed in future.
   */
  boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException;

  default boolean injectMotionEventSequence(Iterable<MotionEvent> events)
      throws InjectEventSecurityException {
    android.util.Log.w(
        "UIC",
        "Using default injectMotionEventSeq() - this may not inject a sequence properly. "
            + "If wrapping UIController please override this method and delegate.");
    Iterator<MotionEvent> mei = events.iterator();
    boolean success = true;
    while (mei.hasNext()) {
      MotionEvent me = mei.next();
      if (me.getEventTime() - SystemClock.uptimeMillis() > 10) {
        // Because the loopMainThreadForAtLeast is overkill for waiting, intentially only
        // call it with a smaller amount of milliseconds as best effort
        loopMainThreadForAtLeast(10);
      }
      success &= injectMotionEvent(me);
    }
    return success;
  }

  /**
   * Injects a key event into the application.
   *
   * @param event the (non-null!) event to inject
   * @return true if the event was injected, false otherwise
   * @throws InjectEventSecurityException if the event couldn't be injected because it would
   *     interact with another application.
   */
  boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException;

  /**
   * Types a string into the application using series of {@link KeyEvent}s. It is up to the
   * implementor to decide how to map the string to {@link KeyEvent} objects. If you need specific
   * control over the key events generated use {@link #injectKeyEvent(KeyEvent)}.
   *
   * @param str the (non-null!) string to type
   * @return true if the string was injected, false otherwise
   * @throws InjectEventSecurityException if the events couldn't be injected because it would
   *     interact with another application.
   */
  boolean injectString(String str) throws InjectEventSecurityException;

  /**
   * Loops the main thread until the application goes idle.
   *
   * <p>An empty task is immediately inserted into the task queue to ensure that if we're idle at
   * this moment we'll return instantly.
   */
  void loopMainThreadUntilIdle();

  /**
   * Loops the main thread for a specified period of time.
   *
   * <p>Control may not return immediately, instead it'll return after the provided delay has passed
   * and the queue is in an idle state again.
   *
   * @param millisDelay time to spend in looping the main thread
   */
  void loopMainThreadForAtLeast(long millisDelay);
}