MotionEventBuilder.java

/*
 * Copyright (C) 2018 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.core.view;

import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import java.util.ArrayList;
import java.util.List;

/**
 * A helper builder for creating {@link MotionEvent}'s.
 *
 * <p>Default values for unspecified attributes are 0 unless otherwise noted.
 */
public class MotionEventBuilder {

  private long downTime = 0;
  private long eventTime = SystemClock.uptimeMillis();
  private int action = MotionEvent.ACTION_DOWN;
  private int actionIndex = -1;
  private List<PointerProperties> pointerPropertiesList = new ArrayList<>();
  private List<PointerCoords> pointerCoordsList = new ArrayList<>();
  private int metaState = 0;
  private int buttonState = 0;
  private float xPrecision = 0f;
  private float yPrecision = 0f;
  private int deviceId = 0;
  private int edgeFlags = 0;
  private int source = 0;
  private int flags = 0;

  private MotionEventBuilder() {}

  /**
   * Start building a new MotionEvent.
   *
   * @return a new MotionEventBuilder.
   */
  public static MotionEventBuilder buildMotionEvent() {
    return new MotionEventBuilder();
  }

  /**
   * Sets the down time.
   *
   * @see MotionEvent#getDownTime()
   */
  public MotionEventBuilder setDownTime(long downTime) {
    this.downTime = downTime;
    return this;
  }

  /**
   * Sets the event time. Default is SystemClock.uptimeMillis().
   *
   * @see MotionEvent#getEventTime()
   */
  public MotionEventBuilder setEventTime(long eventTime) {
    this.eventTime = eventTime;
    return this;
  }

  /**
   * Sets the action. Default is {@link MotionEvent.ACTION_DOWN}.
   *
   * @see MotionEvent#getAction()
   */
  public MotionEventBuilder setAction(int action) {
    this.action = action;
    return this;
  }

  /**
   * Sets the pointer index associated with the action.
   *
   * @see MotionEvent#getActionIndex()
   */
  public MotionEventBuilder setActionIndex(int pointerIndex) {
    checkState(pointerIndex <= 0xFF, "pointerIndex must be less than 0xff");
    this.actionIndex = pointerIndex;
    return this;
  }

  /**
   * Sets the metaState.
   *
   * @see MotionEvent#getMetaState()
   */
  public MotionEventBuilder setMetaState(int metastate) {
    this.metaState = metastate;
    return this;
  }

  /**
   * Sets the button state.
   *
   * @see MotionEvent#getButtonState()
   */
  public MotionEventBuilder setButtonState(int buttonState) {
    this.buttonState = buttonState;
    return this;
  }

  /**
   * Sets the x precision.
   *
   * @see MotionEvent#getXPrecision()
   */
  public MotionEventBuilder setXPrecision(float xPrecision) {
    this.xPrecision = xPrecision;
    return this;
  }

  /**
   * Sets the y precision.
   *
   * @see MotionEvent#getYPrecision()
   */
  public MotionEventBuilder setYPrecision(float yPrecision) {
    this.yPrecision = yPrecision;
    return this;
  }

  /**
   * Sets the device id.
   *
   * @see MotionEvent#getDeviceId()
   */
  public MotionEventBuilder setDeviceId(int deviceId) {
    this.deviceId = deviceId;
    return this;
  }

  /**
   * Sets the edge flags.
   *
   * @see MotionEvent#getEdgeFlags()
   */
  public MotionEventBuilder setEdgeFlags(int edgeFlags) {
    this.edgeFlags = edgeFlags;
    return this;
  }

  /**
   * Sets the source.
   *
   * @see MotionEvent#getSource()
   */
  public MotionEventBuilder setSource(int source) {
    this.source = source;
    return this;
  }

  /**
   * Sets the flags.
   *
   * @see MotionEvent#getFlags()
   */
  public MotionEventBuilder setFlags(int flags) {
    this.flags = flags;
    return this;
  }

  /**
   * Simple mechanism to add a pointer to the MotionEvent.
   *
   * <p>Can be called multiple times to add multiple pointers to the event.
   */
  public MotionEventBuilder setPointer(float x, float y) {
    PointerProperties pointerProperties = new PointerProperties();
    pointerProperties.id = pointerPropertiesList.size();
    PointerCoords pointerCoords = new PointerCoords();
    pointerCoords.x = x;
    pointerCoords.y = y;
    return setPointer(pointerProperties, pointerCoords);
  }

  /**
   * An expanded variant of {@link #setPointer(float, float)} that supports specifying all pointer
   * properties and coords data.
   */
  public MotionEventBuilder setPointer(
      PointerProperties pointerProperties, PointerCoords pointerCoords) {
    pointerPropertiesList.add(pointerProperties);
    pointerCoordsList.add(pointerCoords);
    return this;
  }

  /** Returns a MotionEvent with the provided data or reasonable defaults. */
  public MotionEvent build() {
    if (pointerPropertiesList.size() == 0) {
      setPointer(0, 0);
    }
    if (actionIndex != -1) {
      action = action | (actionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    }
    return MotionEvent.obtain(
        downTime,
        eventTime,
        action,
        pointerPropertiesList.size(),
        pointerPropertiesList.toArray(new PointerProperties[pointerPropertiesList.size()]),
        pointerCoordsList.toArray(new MotionEvent.PointerCoords[pointerCoordsList.size()]),
        metaState,
        buttonState,
        xPrecision,
        yPrecision,
        deviceId,
        edgeFlags,
        source,
        flags);
  }

  private static void checkState(boolean expression, String message) {
    if (!expression) {
      throw new IllegalStateException(message);
    }
  }
}